use HeaderValue instead of []string; edit ReadHeaderAuth(), ReadHeaderSession(), ReadHeaderTransport() to accept HeaderValue

This commit is contained in:
aler9
2020-07-18 13:33:58 +02:00
parent aed1445226
commit 4754822be1
12 changed files with 151 additions and 124 deletions

60
auth.go
View File

@@ -55,8 +55,8 @@ func NewAuthServer(user string, pass string, methods []AuthMethod) *AuthServer {
} }
// GenerateHeader generates the WWW-Authenticate header needed by a client to log in. // GenerateHeader generates the WWW-Authenticate header needed by a client to log in.
func (as *AuthServer) GenerateHeader() []string { func (as *AuthServer) GenerateHeader() HeaderValue {
var ret []string var ret HeaderValue
for _, m := range as.methods { for _, m := range as.methods {
switch m { switch m {
case Basic: case Basic:
@@ -65,7 +65,7 @@ func (as *AuthServer) GenerateHeader() []string {
Values: map[string]string{ Values: map[string]string{
"realm": as.realm, "realm": as.realm,
}, },
}).Write()) }).Write()...)
case Digest: case Digest:
ret = append(ret, (&HeaderAuth{ ret = append(ret, (&HeaderAuth{
@@ -74,7 +74,7 @@ func (as *AuthServer) GenerateHeader() []string {
"realm": as.realm, "realm": as.realm,
"nonce": as.nonce, "nonce": as.nonce,
}, },
}).Write()) }).Write()...)
} }
} }
return ret return ret
@@ -82,18 +82,18 @@ func (as *AuthServer) GenerateHeader() []string {
// ValidateHeader validates the Authorization header sent by a client after receiving the // ValidateHeader validates the Authorization header sent by a client after receiving the
// WWW-Authenticate header provided by GenerateHeader(). // WWW-Authenticate header provided by GenerateHeader().
func (as *AuthServer) ValidateHeader(header []string, method Method, ur *url.URL) error { func (as *AuthServer) ValidateHeader(v HeaderValue, method Method, ur *url.URL) error {
if len(header) == 0 { if len(v) == 0 {
return fmt.Errorf("authorization header not provided") return fmt.Errorf("authorization header not provided")
} }
if len(header) > 1 { if len(v) > 1 {
return fmt.Errorf("authorization header provided multiple times") return fmt.Errorf("authorization header provided multiple times")
} }
head := header[0] v0 := v[0]
if strings.HasPrefix(head, "Basic ") { if strings.HasPrefix(v0, "Basic ") {
inResponse := head[len("Basic "):] inResponse := v0[len("Basic "):]
response := base64.StdEncoding.EncodeToString([]byte(as.user + ":" + as.pass)) response := base64.StdEncoding.EncodeToString([]byte(as.user + ":" + as.pass))
@@ -101,8 +101,8 @@ func (as *AuthServer) ValidateHeader(header []string, method Method, ur *url.URL
return fmt.Errorf("wrong response") return fmt.Errorf("wrong response")
} }
} else if strings.HasPrefix(head, "Digest ") { } else if strings.HasPrefix(v0, "Digest ") {
auth, err := ReadHeaderAuth(head) auth, err := ReadHeaderAuth(HeaderValue{v0})
if err != nil { if err != nil {
return err return err
} }
@@ -191,18 +191,17 @@ type authClient struct {
// newAuthClient allocates an authClient. // newAuthClient allocates an authClient.
// header is the WWW-Authenticate header provided by the server. // header is the WWW-Authenticate header provided by the server.
func newAuthClient(header []string, user string, pass string) (*authClient, error) { func newAuthClient(v HeaderValue, user string, pass string) (*authClient, error) {
// prefer digest // prefer digest
headerAuthDigest := func() string { if headerAuthDigest := func() string {
for _, v := range header { for _, vi := range v {
if strings.HasPrefix(v, "Digest ") { if strings.HasPrefix(vi, "Digest ") {
return v return vi
} }
} }
return "" return ""
}() }(); headerAuthDigest != "" {
if headerAuthDigest != "" { auth, err := ReadHeaderAuth(HeaderValue{headerAuthDigest})
auth, err := ReadHeaderAuth(headerAuthDigest)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -226,16 +225,15 @@ func newAuthClient(header []string, user string, pass string) (*authClient, erro
}, nil }, nil
} }
headerAuthBasic := func() string { if headerAuthBasic := func() string {
for _, v := range header { for _, vi := range v {
if strings.HasPrefix(v, "Basic ") { if strings.HasPrefix(vi, "Basic ") {
return v return vi
} }
} }
return "" return ""
}() }(); headerAuthBasic != "" {
if headerAuthBasic != "" { auth, err := ReadHeaderAuth(HeaderValue{headerAuthBasic})
auth, err := ReadHeaderAuth(headerAuthBasic)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -258,18 +256,18 @@ func newAuthClient(header []string, user string, pass string) (*authClient, erro
// GenerateHeader generates an Authorization Header that allows to authenticate a request with // GenerateHeader generates an Authorization Header that allows to authenticate a request with
// the given method and url. // the given method and url.
func (ac *authClient) GenerateHeader(method Method, ur *url.URL) []string { func (ac *authClient) GenerateHeader(method Method, ur *url.URL) HeaderValue {
switch ac.method { switch ac.method {
case Basic: case Basic:
response := base64.StdEncoding.EncodeToString([]byte(ac.user + ":" + ac.pass)) response := base64.StdEncoding.EncodeToString([]byte(ac.user + ":" + ac.pass))
return []string{"Basic " + response} return HeaderValue{"Basic " + response}
case Digest: case Digest:
response := md5Hex(md5Hex(ac.user+":"+ac.realm+":"+ac.pass) + ":" + response := md5Hex(md5Hex(ac.user+":"+ac.realm+":"+ac.pass) + ":" +
ac.nonce + ":" + md5Hex(string(method)+":"+ur.String())) ac.nonce + ":" + md5Hex(string(method)+":"+ur.String()))
return []string{(&HeaderAuth{ return (&HeaderAuth{
Prefix: "Digest", Prefix: "Digest",
Values: map[string]string{ Values: map[string]string{
"username": ac.user, "username": ac.user,
@@ -278,7 +276,7 @@ func (ac *authClient) GenerateHeader(method Method, ur *url.URL) []string {
"uri": ur.String(), "uri": ur.String(),
"response": response, "response": response,
}, },
}).Write()} }).Write()
} }
return nil return nil

View File

@@ -112,7 +112,7 @@ func (c *ConnClient) Do(req *Request) (*Response, error) {
// insert session // insert session
if c.session != "" { if c.session != "" {
req.Header["Session"] = []string{c.session} req.Header["Session"] = HeaderValue{c.session}
} }
// insert auth // insert auth
@@ -129,7 +129,7 @@ func (c *ConnClient) Do(req *Request) (*Response, error) {
// insert cseq // insert cseq
c.curCSeq += 1 c.curCSeq += 1
req.Header["CSeq"] = []string{strconv.FormatInt(int64(c.curCSeq), 10)} req.Header["CSeq"] = HeaderValue{strconv.FormatInt(int64(c.curCSeq), 10)}
c.conf.Conn.SetWriteDeadline(time.Now().Add(c.conf.WriteTimeout)) c.conf.Conn.SetWriteDeadline(time.Now().Add(c.conf.WriteTimeout))
err := req.write(c.bw) err := req.write(c.bw)
@@ -148,8 +148,8 @@ func (c *ConnClient) Do(req *Request) (*Response, error) {
} }
// get session from response // get session from response
if sxRaw, ok := res.Header["Session"]; ok && len(sxRaw) == 1 { if v, ok := res.Header["Session"]; ok {
sx, err := ReadHeaderSession(sxRaw[0]) sx, err := ReadHeaderSession(v)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to parse session header: %s", err) return nil, fmt.Errorf("unable to parse session header: %s", err)
} }
@@ -301,7 +301,7 @@ func (c *ConnClient) setup(u *url.URL, media *sdp.MediaDescription, transport []
Method: SETUP, Method: SETUP,
Url: u, Url: u,
Header: Header{ Header: Header{
"Transport": []string{strings.Join(transport, ";")}, "Transport": HeaderValue{strings.Join(transport, ";")},
}, },
}) })
if err != nil { if err != nil {
@@ -329,12 +329,11 @@ func (c *ConnClient) SetupUdp(u *url.URL, track *Track, rtpPort int,
return 0, 0, nil, err return 0, 0, nil, err
} }
tsRaw, ok := res.Header["Transport"] th, err := ReadHeaderTransport(res.Header["Transport"])
if !ok || len(tsRaw) != 1 { if err != nil {
return 0, 0, nil, fmt.Errorf("SETUP: transport header not provided") return 0, 0, nil, fmt.Errorf("SETUP: transport header: %s", err)
} }
th := ReadHeaderTransport(tsRaw[0])
rtpServerPort, rtcpServerPort := th.GetPorts("server_port") rtpServerPort, rtcpServerPort := th.GetPorts("server_port")
if rtpServerPort == 0 { if rtpServerPort == 0 {
return 0, 0, nil, fmt.Errorf("SETUP: server ports not provided") return 0, 0, nil, fmt.Errorf("SETUP: server ports not provided")
@@ -357,15 +356,14 @@ func (c *ConnClient) SetupTcp(u *url.URL, track *Track) (*Response, error) {
return nil, err return nil, err
} }
tsRaw, ok := res.Header["Transport"] th, err := ReadHeaderTransport(res.Header["Transport"])
if !ok || len(tsRaw) != 1 { if err != nil {
return nil, fmt.Errorf("SETUP: transport header not provided") return nil, fmt.Errorf("SETUP: transport header: %s", err)
} }
th := ReadHeaderTransport(tsRaw[0])
_, ok = th[interleaved] _, ok := th[interleaved]
if !ok { if !ok {
return nil, fmt.Errorf("SETUP: transport header does not have %s (%s)", interleaved, tsRaw[0]) return nil, fmt.Errorf("SETUP: transport header does not have %s (%s)", interleaved, res.Header["Transport"])
} }
return res, nil return res, nil

View File

@@ -16,23 +16,33 @@ type HeaderAuth struct {
var regHeaderAuthKeyValue = regexp.MustCompile("^([a-z]+)=(\"(.*?)\"|([a-zA-Z0-9]+))(, *|$)") var regHeaderAuthKeyValue = regexp.MustCompile("^([a-z]+)=(\"(.*?)\"|([a-zA-Z0-9]+))(, *|$)")
// ReadHeaderAuth parses an Authenticate or a WWW-Authenticate header. // ReadHeaderAuth parses an Authenticate or a WWW-Authenticate header.
func ReadHeaderAuth(in string) (*HeaderAuth, error) { func ReadHeaderAuth(v HeaderValue) (*HeaderAuth, error) {
if len(v) == 0 {
return nil, fmt.Errorf("value not provided")
}
if len(v) > 1 {
return nil, fmt.Errorf("value provided multiple times (%v)", v)
}
ha := &HeaderAuth{ ha := &HeaderAuth{
Values: make(map[string]string), Values: make(map[string]string),
} }
i := strings.IndexByte(in, ' ') v0 := v[0]
if i < 0 {
return nil, fmt.Errorf("unable to find prefix (%s)", in)
}
ha.Prefix, in = in[:i], in[i+1:]
for len(in) > 0 { i := strings.IndexByte(v[0], ' ')
m := regHeaderAuthKeyValue.FindStringSubmatch(in) if i < 0 {
if m == nil { return nil, fmt.Errorf("unable to find prefix (%s)", v0)
return nil, fmt.Errorf("unable to parse key-value (%s)", in)
} }
in = in[len(m[0]):] ha.Prefix, v0 = v0[:i], v0[i+1:]
for len(v0) > 0 {
m := regHeaderAuthKeyValue.FindStringSubmatch(v0)
if m == nil {
return nil, fmt.Errorf("unable to parse key-value (%s)", v0)
}
v0 = v0[len(m[0]):]
m[2] = strings.TrimPrefix(m[2], "\"") m[2] = strings.TrimPrefix(m[2], "\"")
m[2] = strings.TrimSuffix(m[2], "\"") m[2] = strings.TrimSuffix(m[2], "\"")
@@ -43,7 +53,7 @@ func ReadHeaderAuth(in string) (*HeaderAuth, error) {
} }
// Write encodes an Authenticate or a WWW-Authenticate header. // Write encodes an Authenticate or a WWW-Authenticate header.
func (ha *HeaderAuth) Write() string { func (ha *HeaderAuth) Write() HeaderValue {
ret := ha.Prefix + " " ret := ha.Prefix + " "
// always put realm first, otherwise VLC does not send back the response // always put realm first, otherwise VLC does not send back the response
@@ -64,5 +74,5 @@ func (ha *HeaderAuth) Write() string {
} }
ret += strings.Join(tmp, ", ") ret += strings.Join(tmp, ", ")
return ret return HeaderValue{ret}
} }

View File

@@ -8,14 +8,14 @@ import (
var casesHeaderAuth = []struct { var casesHeaderAuth = []struct {
name string name string
dec string dec HeaderValue
enc string enc HeaderValue
ha *HeaderAuth ha *HeaderAuth
}{ }{
{ {
"basic", "basic",
`Basic realm="4419b63f5e51"`, HeaderValue{`Basic realm="4419b63f5e51"`},
`Basic realm="4419b63f5e51"`, HeaderValue{`Basic realm="4419b63f5e51"`},
&HeaderAuth{ &HeaderAuth{
Prefix: "Basic", Prefix: "Basic",
Values: map[string]string{ Values: map[string]string{
@@ -25,8 +25,8 @@ var casesHeaderAuth = []struct {
}, },
{ {
"digest request 1", "digest request 1",
`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`, HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`},
`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`, HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`},
&HeaderAuth{ &HeaderAuth{
Prefix: "Digest", Prefix: "Digest",
Values: map[string]string{ Values: map[string]string{
@@ -38,8 +38,8 @@ var casesHeaderAuth = []struct {
}, },
{ {
"digest request 2", "digest request 2",
`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale=FALSE`, HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale=FALSE`},
`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`, HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`},
&HeaderAuth{ &HeaderAuth{
Prefix: "Digest", Prefix: "Digest",
Values: map[string]string{ Values: map[string]string{
@@ -51,8 +51,8 @@ var casesHeaderAuth = []struct {
}, },
{ {
"digest request 3", "digest request 3",
`Digest realm="4419b63f5e51",nonce="133767111917411116111311118211673010032", stale="FALSE"`, HeaderValue{`Digest realm="4419b63f5e51",nonce="133767111917411116111311118211673010032", stale="FALSE"`},
`Digest realm="4419b63f5e51", nonce="133767111917411116111311118211673010032", stale="FALSE"`, HeaderValue{`Digest realm="4419b63f5e51", nonce="133767111917411116111311118211673010032", stale="FALSE"`},
&HeaderAuth{ &HeaderAuth{
Prefix: "Digest", Prefix: "Digest",
Values: map[string]string{ Values: map[string]string{
@@ -64,8 +64,8 @@ var casesHeaderAuth = []struct {
}, },
{ {
"digest response generic", "digest response generic",
`Digest username="aa", realm="bb", nonce="cc", uri="dd", response="ee"`, HeaderValue{`Digest username="aa", realm="bb", nonce="cc", uri="dd", response="ee"`},
`Digest realm="bb", nonce="cc", response="ee", uri="dd", username="aa"`, HeaderValue{`Digest realm="bb", nonce="cc", response="ee", uri="dd", username="aa"`},
&HeaderAuth{ &HeaderAuth{
Prefix: "Digest", Prefix: "Digest",
Values: map[string]string{ Values: map[string]string{
@@ -79,8 +79,8 @@ var casesHeaderAuth = []struct {
}, },
{ {
"digest response with empty field", "digest response with empty field",
`Digest username="", realm="IPCAM", nonce="5d17cd12b9fa8a85ac5ceef0926ea5a6", uri="rtsp://localhost:8554/mystream", response="c072ae90eb4a27f4cdcb90d62266b2a1"`, HeaderValue{`Digest username="", realm="IPCAM", nonce="5d17cd12b9fa8a85ac5ceef0926ea5a6", uri="rtsp://localhost:8554/mystream", response="c072ae90eb4a27f4cdcb90d62266b2a1"`},
`Digest realm="IPCAM", nonce="5d17cd12b9fa8a85ac5ceef0926ea5a6", response="c072ae90eb4a27f4cdcb90d62266b2a1", uri="rtsp://localhost:8554/mystream", username=""`, HeaderValue{`Digest realm="IPCAM", nonce="5d17cd12b9fa8a85ac5ceef0926ea5a6", response="c072ae90eb4a27f4cdcb90d62266b2a1", uri="rtsp://localhost:8554/mystream", username=""`},
&HeaderAuth{ &HeaderAuth{
Prefix: "Digest", Prefix: "Digest",
Values: map[string]string{ Values: map[string]string{
@@ -94,8 +94,8 @@ var casesHeaderAuth = []struct {
}, },
{ {
"digest response with no spaces and additional fields", "digest response with no spaces and additional fields",
`Digest realm="Please log in with a valid username",nonce="752a62306daf32b401a41004555c7663",opaque="",stale=FALSE,algorithm=MD5`, HeaderValue{`Digest realm="Please log in with a valid username",nonce="752a62306daf32b401a41004555c7663",opaque="",stale=FALSE,algorithm=MD5`},
`Digest realm="Please log in with a valid username", algorithm="MD5", nonce="752a62306daf32b401a41004555c7663", opaque="", stale="FALSE"`, HeaderValue{`Digest realm="Please log in with a valid username", algorithm="MD5", nonce="752a62306daf32b401a41004555c7663", opaque="", stale="FALSE"`},
&HeaderAuth{ &HeaderAuth{
Prefix: "Digest", Prefix: "Digest",
Values: map[string]string{ Values: map[string]string{

View File

@@ -13,8 +13,16 @@ type HeaderSession struct {
} }
// ReadHeaderSession parses a Session header. // ReadHeaderSession parses a Session header.
func ReadHeaderSession(in string) (*HeaderSession, error) { func ReadHeaderSession(v HeaderValue) (*HeaderSession, error) {
parts := strings.Split(in, ";") if len(v) == 0 {
return nil, fmt.Errorf("value not provided")
}
if len(v) > 1 {
return nil, fmt.Errorf("value provided multiple times (%v)", v)
}
parts := strings.Split(v[0], ";")
if len(parts) == 0 { if len(parts) == 0 {
return nil, fmt.Errorf("invalid value") return nil, fmt.Errorf("invalid value")
} }

View File

@@ -8,19 +8,19 @@ import (
var casesHeaderSession = []struct { var casesHeaderSession = []struct {
name string name string
byts string value HeaderValue
hs *HeaderSession hs *HeaderSession
}{ }{
{ {
"base", "base",
`A3eqwsafq3rFASqew`, HeaderValue{`A3eqwsafq3rFASqew`},
&HeaderSession{ &HeaderSession{
Session: "A3eqwsafq3rFASqew", Session: "A3eqwsafq3rFASqew",
}, },
}, },
{ {
"with timeout", "with timeout",
`A3eqwsafq3rFASqew;timeout=47`, HeaderValue{`A3eqwsafq3rFASqew;timeout=47`},
&HeaderSession{ &HeaderSession{
Session: "A3eqwsafq3rFASqew", Session: "A3eqwsafq3rFASqew",
Timeout: func() *uint { Timeout: func() *uint {
@@ -31,7 +31,7 @@ var casesHeaderSession = []struct {
}, },
{ {
"with timeout and space", "with timeout and space",
`A3eqwsafq3rFASqew; timeout=47`, HeaderValue{`A3eqwsafq3rFASqew; timeout=47`},
&HeaderSession{ &HeaderSession{
Session: "A3eqwsafq3rFASqew", Session: "A3eqwsafq3rFASqew",
Timeout: func() *uint { Timeout: func() *uint {
@@ -45,7 +45,7 @@ var casesHeaderSession = []struct {
func TestHeaderSession(t *testing.T) { func TestHeaderSession(t *testing.T) {
for _, c := range casesHeaderSession { for _, c := range casesHeaderSession {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
req, err := ReadHeaderSession(c.byts) req, err := ReadHeaderSession(c.value)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, c.hs, req) require.Equal(t, c.hs, req)
}) })

View File

@@ -1,6 +1,7 @@
package gortsplib package gortsplib
import ( import (
"fmt"
"strconv" "strconv"
"strings" "strings"
) )
@@ -9,12 +10,21 @@ import (
type HeaderTransport map[string]struct{} type HeaderTransport map[string]struct{}
// ReadHeaderTransport parses a Transport header. // ReadHeaderTransport parses a Transport header.
func ReadHeaderTransport(in string) HeaderTransport { func ReadHeaderTransport(v HeaderValue) (HeaderTransport, error) {
if len(v) == 0 {
return nil, fmt.Errorf("value not provided")
}
if len(v) > 1 {
return nil, fmt.Errorf("value provided multiple times (%v)", v)
}
ht := make(map[string]struct{}) ht := make(map[string]struct{})
for _, t := range strings.Split(in, ";") { for _, t := range strings.Split(v[0], ";") {
ht[t] = struct{}{} ht[t] = struct{}{}
} }
return ht
return ht, nil
} }
// GetValue gets a value from the header. // GetValue gets a value from the header.

View File

@@ -28,8 +28,11 @@ func headerKeyNormalize(in string) string {
return http.CanonicalHeaderKey(in) return http.CanonicalHeaderKey(in)
} }
// HeaderValue is an header value.
type HeaderValue []string
// Header is a RTSP reader, present in both Requests and Responses. // Header is a RTSP reader, present in both Requests and Responses.
type Header map[string][]string type Header map[string]HeaderValue
func headerRead(rb *bufio.Reader) (Header, error) { func headerRead(rb *bufio.Reader) (Header, error) {
h := make(Header) h := make(Header)

View File

@@ -23,8 +23,8 @@ var casesHeader = []struct {
"Require: implicit-play\r\n" + "Require: implicit-play\r\n" +
"\r\n"), "\r\n"),
Header{ Header{
"Require": []string{"implicit-play"}, "Require": HeaderValue{"implicit-play"},
"Proxy-Require": []string{"gzipped-messages"}, "Proxy-Require": HeaderValue{"gzipped-messages"},
}, },
}, },
{ {
@@ -36,7 +36,7 @@ var casesHeader = []struct {
"WWW-Authenticate: Basic realm=\"4419b63f5e51\"\r\n" + "WWW-Authenticate: Basic realm=\"4419b63f5e51\"\r\n" +
"\r\n"), "\r\n"),
Header{ Header{
"WWW-Authenticate": []string{ "WWW-Authenticate": HeaderValue{
`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`, `Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`,
`Basic realm="4419b63f5e51"`, `Basic realm="4419b63f5e51"`,
}, },
@@ -49,7 +49,7 @@ var casesHeader = []struct {
[]byte("CSeq: 2\r\n" + []byte("CSeq: 2\r\n" +
"\r\n"), "\r\n"),
Header{ Header{
"CSeq": []string{"2"}, "CSeq": HeaderValue{"2"},
}, },
}, },
{ {
@@ -59,7 +59,7 @@ var casesHeader = []struct {
[]byte("CSeq: 2\r\n" + []byte("CSeq: 2\r\n" +
"\r\n"), "\r\n"),
Header{ Header{
"CSeq": []string{"2"}, "CSeq": HeaderValue{"2"},
}, },
}, },
{ {
@@ -71,8 +71,8 @@ var casesHeader = []struct {
"Content-Type: testing\r\n" + "Content-Type: testing\r\n" +
"\r\n"), "\r\n"),
Header{ Header{
"Content-Length": []string{"value"}, "Content-Length": HeaderValue{"value"},
"Content-Type": []string{"testing"}, "Content-Type": HeaderValue{"testing"},
}, },
}, },
{ {
@@ -84,8 +84,8 @@ var casesHeader = []struct {
"WWW-Authenticate: value\r\n" + "WWW-Authenticate: value\r\n" +
"\r\n"), "\r\n"),
Header{ Header{
"CSeq": []string{"value"}, "CSeq": HeaderValue{"value"},
"WWW-Authenticate": []string{"value"}, "WWW-Authenticate": HeaderValue{"value"},
}, },
}, },
} }

View File

@@ -25,9 +25,9 @@ var casesRequest = []struct {
Method: "OPTIONS", Method: "OPTIONS",
Url: &url.URL{Scheme: "rtsp", Host: "example.com", Path: "/media.mp4"}, Url: &url.URL{Scheme: "rtsp", Host: "example.com", Path: "/media.mp4"},
Header: Header{ Header: Header{
"CSeq": []string{"1"}, "CSeq": HeaderValue{"1"},
"Require": []string{"implicit-play"}, "Require": HeaderValue{"implicit-play"},
"Proxy-Require": []string{"gzipped-messages"}, "Proxy-Require": HeaderValue{"gzipped-messages"},
}, },
}, },
}, },
@@ -40,7 +40,7 @@ var casesRequest = []struct {
Method: "DESCRIBE", Method: "DESCRIBE",
Url: &url.URL{Scheme: "rtsp", Host: "example.com", Path: "/media.mp4"}, Url: &url.URL{Scheme: "rtsp", Host: "example.com", Path: "/media.mp4"},
Header: Header{ Header: Header{
"CSeq": []string{"2"}, "CSeq": HeaderValue{"2"},
}, },
}, },
}, },
@@ -68,11 +68,11 @@ var casesRequest = []struct {
Method: "ANNOUNCE", Method: "ANNOUNCE",
Url: &url.URL{Scheme: "rtsp", Host: "example.com", Path: "/media.mp4"}, Url: &url.URL{Scheme: "rtsp", Host: "example.com", Path: "/media.mp4"},
Header: Header{ Header: Header{
"CSeq": []string{"7"}, "CSeq": HeaderValue{"7"},
"Date": []string{"23 Jan 1997 15:35:06 GMT"}, "Date": HeaderValue{"23 Jan 1997 15:35:06 GMT"},
"Session": []string{"12345678"}, "Session": HeaderValue{"12345678"},
"Content-Type": []string{"application/sdp"}, "Content-Type": HeaderValue{"application/sdp"},
"Content-Length": []string{"306"}, "Content-Length": HeaderValue{"306"},
}, },
Content: []byte("v=0\n" + Content: []byte("v=0\n" +
"o=mhandley 2890844526 2890845468 IN IP4 126.16.64.4\n" + "o=mhandley 2890844526 2890845468 IN IP4 126.16.64.4\n" +
@@ -102,10 +102,10 @@ var casesRequest = []struct {
Method: "GET_PARAMETER", Method: "GET_PARAMETER",
Url: &url.URL{Scheme: "rtsp", Host: "example.com", Path: "/media.mp4"}, Url: &url.URL{Scheme: "rtsp", Host: "example.com", Path: "/media.mp4"},
Header: Header{ Header: Header{
"CSeq": []string{"9"}, "CSeq": HeaderValue{"9"},
"Content-Type": []string{"text/parameters"}, "Content-Type": HeaderValue{"text/parameters"},
"Session": []string{"12345678"}, "Session": HeaderValue{"12345678"},
"Content-Length": []string{"24"}, "Content-Length": HeaderValue{"24"},
}, },
Content: []byte("packets_received\n" + Content: []byte("packets_received\n" +
"jitter\n", "jitter\n",

View File

@@ -198,7 +198,7 @@ func (res *Response) write(bw *bufio.Writer) error {
} }
if len(res.Content) != 0 { if len(res.Content) != 0 {
res.Header["Content-Length"] = []string{strconv.FormatInt(int64(len(res.Content)), 10)} res.Header["Content-Length"] = HeaderValue{strconv.FormatInt(int64(len(res.Content)), 10)}
} }
err = res.Header.write(bw) err = res.Header.write(bw)

View File

@@ -24,8 +24,8 @@ var casesResponse = []struct {
StatusCode: StatusOK, StatusCode: StatusOK,
StatusMessage: "OK", StatusMessage: "OK",
Header: Header{ Header: Header{
"CSeq": []string{"1"}, "CSeq": HeaderValue{"1"},
"Public": []string{"DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE"}, "Public": HeaderValue{"DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE"},
}, },
}, },
}, },
@@ -43,13 +43,13 @@ var casesResponse = []struct {
StatusCode: StatusOK, StatusCode: StatusOK,
StatusMessage: "OK", StatusMessage: "OK",
Header: Header{ Header: Header{
"CSeq": []string{"2"}, "CSeq": HeaderValue{"2"},
"Session": []string{"645252166"}, "Session": HeaderValue{"645252166"},
"WWW-Authenticate": []string{ "WWW-Authenticate": HeaderValue{
"Digest realm=\"4419b63f5e51\", nonce=\"8b84a3b789283a8bea8da7fa7d41f08b\", stale=\"FALSE\"", "Digest realm=\"4419b63f5e51\", nonce=\"8b84a3b789283a8bea8da7fa7d41f08b\", stale=\"FALSE\"",
"Basic realm=\"4419b63f5e51\"", "Basic realm=\"4419b63f5e51\"",
}, },
"Date": []string{"Sat, Aug 16 2014 02:22:28 GMT"}, "Date": HeaderValue{"Sat, Aug 16 2014 02:22:28 GMT"},
}, },
}, },
}, },
@@ -82,10 +82,10 @@ var casesResponse = []struct {
StatusCode: 200, StatusCode: 200,
StatusMessage: "OK", StatusMessage: "OK",
Header: Header{ Header: Header{
"Content-Base": []string{"rtsp://example.com/media.mp4"}, "Content-Base": HeaderValue{"rtsp://example.com/media.mp4"},
"Content-Length": []string{"444"}, "Content-Length": HeaderValue{"444"},
"Content-Type": []string{"application/sdp"}, "Content-Type": HeaderValue{"application/sdp"},
"CSeq": []string{"2"}, "CSeq": HeaderValue{"2"},
}, },
Content: []byte("m=video 0 RTP/AVP 96\n" + Content: []byte("m=video 0 RTP/AVP 96\n" +
"a=control:streamid=0\n" + "a=control:streamid=0\n" +
@@ -135,13 +135,13 @@ func TestResponseWriteStatusAutofill(t *testing.T) {
res := &Response{ res := &Response{
StatusCode: StatusMethodNotAllowed, StatusCode: StatusMethodNotAllowed,
Header: Header{ Header: Header{
"CSeq": []string{"2"}, "CSeq": HeaderValue{"2"},
"Session": []string{"645252166"}, "Session": HeaderValue{"645252166"},
"WWW-Authenticate": []string{ "WWW-Authenticate": HeaderValue{
"Digest realm=\"4419b63f5e51\", nonce=\"8b84a3b789283a8bea8da7fa7d41f08b\", stale=\"FALSE\"", "Digest realm=\"4419b63f5e51\", nonce=\"8b84a3b789283a8bea8da7fa7d41f08b\", stale=\"FALSE\"",
"Basic realm=\"4419b63f5e51\"", "Basic realm=\"4419b63f5e51\"",
}, },
"Date": []string{"Sat, Aug 16 2014 02:22:28 GMT"}, "Date": HeaderValue{"Sat, Aug 16 2014 02:22:28 GMT"},
}, },
} }
byts := []byte("RTSP/1.0 405 Method Not Allowed\r\n" + byts := []byte("RTSP/1.0 405 Method Not Allowed\r\n" +