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

View File

@@ -112,7 +112,7 @@ func (c *ConnClient) Do(req *Request) (*Response, error) {
// insert session
if c.session != "" {
req.Header["Session"] = []string{c.session}
req.Header["Session"] = HeaderValue{c.session}
}
// insert auth
@@ -129,7 +129,7 @@ func (c *ConnClient) Do(req *Request) (*Response, error) {
// insert cseq
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))
err := req.write(c.bw)
@@ -148,8 +148,8 @@ func (c *ConnClient) Do(req *Request) (*Response, error) {
}
// get session from response
if sxRaw, ok := res.Header["Session"]; ok && len(sxRaw) == 1 {
sx, err := ReadHeaderSession(sxRaw[0])
if v, ok := res.Header["Session"]; ok {
sx, err := ReadHeaderSession(v)
if err != nil {
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,
Url: u,
Header: Header{
"Transport": []string{strings.Join(transport, ";")},
"Transport": HeaderValue{strings.Join(transport, ";")},
},
})
if err != nil {
@@ -329,12 +329,11 @@ func (c *ConnClient) SetupUdp(u *url.URL, track *Track, rtpPort int,
return 0, 0, nil, err
}
tsRaw, ok := res.Header["Transport"]
if !ok || len(tsRaw) != 1 {
return 0, 0, nil, fmt.Errorf("SETUP: transport header not provided")
th, err := ReadHeaderTransport(res.Header["Transport"])
if err != nil {
return 0, 0, nil, fmt.Errorf("SETUP: transport header: %s", err)
}
th := ReadHeaderTransport(tsRaw[0])
rtpServerPort, rtcpServerPort := th.GetPorts("server_port")
if rtpServerPort == 0 {
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
}
tsRaw, ok := res.Header["Transport"]
if !ok || len(tsRaw) != 1 {
return nil, fmt.Errorf("SETUP: transport header not provided")
th, err := ReadHeaderTransport(res.Header["Transport"])
if err != nil {
return nil, fmt.Errorf("SETUP: transport header: %s", err)
}
th := ReadHeaderTransport(tsRaw[0])
_, ok = th[interleaved]
_, ok := th[interleaved]
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

View File

@@ -16,23 +16,33 @@ type HeaderAuth struct {
var regHeaderAuthKeyValue = regexp.MustCompile("^([a-z]+)=(\"(.*?)\"|([a-zA-Z0-9]+))(, *|$)")
// 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{
Values: make(map[string]string),
}
i := strings.IndexByte(in, ' ')
if i < 0 {
return nil, fmt.Errorf("unable to find prefix (%s)", in)
}
ha.Prefix, in = in[:i], in[i+1:]
v0 := v[0]
for len(in) > 0 {
m := regHeaderAuthKeyValue.FindStringSubmatch(in)
if m == nil {
return nil, fmt.Errorf("unable to parse key-value (%s)", in)
i := strings.IndexByte(v[0], ' ')
if i < 0 {
return nil, fmt.Errorf("unable to find prefix (%s)", v0)
}
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.TrimSuffix(m[2], "\"")
@@ -43,7 +53,7 @@ func ReadHeaderAuth(in string) (*HeaderAuth, error) {
}
// Write encodes an Authenticate or a WWW-Authenticate header.
func (ha *HeaderAuth) Write() string {
func (ha *HeaderAuth) Write() HeaderValue {
ret := ha.Prefix + " "
// 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, ", ")
return ret
return HeaderValue{ret}
}

View File

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

View File

@@ -13,8 +13,16 @@ type HeaderSession struct {
}
// ReadHeaderSession parses a Session header.
func ReadHeaderSession(in string) (*HeaderSession, error) {
parts := strings.Split(in, ";")
func ReadHeaderSession(v HeaderValue) (*HeaderSession, 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)
}
parts := strings.Split(v[0], ";")
if len(parts) == 0 {
return nil, fmt.Errorf("invalid value")
}

View File

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

View File

@@ -1,6 +1,7 @@
package gortsplib
import (
"fmt"
"strconv"
"strings"
)
@@ -9,12 +10,21 @@ import (
type HeaderTransport map[string]struct{}
// 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{})
for _, t := range strings.Split(in, ";") {
for _, t := range strings.Split(v[0], ";") {
ht[t] = struct{}{}
}
return ht
return ht, nil
}
// GetValue gets a value from the header.

View File

@@ -28,8 +28,11 @@ func headerKeyNormalize(in string) string {
return http.CanonicalHeaderKey(in)
}
// HeaderValue is an header value.
type HeaderValue []string
// 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) {
h := make(Header)

View File

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

View File

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

View File

@@ -198,7 +198,7 @@ func (res *Response) write(bw *bufio.Writer) error {
}
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)

View File

@@ -24,8 +24,8 @@ var casesResponse = []struct {
StatusCode: StatusOK,
StatusMessage: "OK",
Header: Header{
"CSeq": []string{"1"},
"Public": []string{"DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE"},
"CSeq": HeaderValue{"1"},
"Public": HeaderValue{"DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE"},
},
},
},
@@ -43,13 +43,13 @@ var casesResponse = []struct {
StatusCode: StatusOK,
StatusMessage: "OK",
Header: Header{
"CSeq": []string{"2"},
"Session": []string{"645252166"},
"WWW-Authenticate": []string{
"CSeq": HeaderValue{"2"},
"Session": HeaderValue{"645252166"},
"WWW-Authenticate": HeaderValue{
"Digest realm=\"4419b63f5e51\", nonce=\"8b84a3b789283a8bea8da7fa7d41f08b\", stale=\"FALSE\"",
"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,
StatusMessage: "OK",
Header: Header{
"Content-Base": []string{"rtsp://example.com/media.mp4"},
"Content-Length": []string{"444"},
"Content-Type": []string{"application/sdp"},
"CSeq": []string{"2"},
"Content-Base": HeaderValue{"rtsp://example.com/media.mp4"},
"Content-Length": HeaderValue{"444"},
"Content-Type": HeaderValue{"application/sdp"},
"CSeq": HeaderValue{"2"},
},
Content: []byte("m=video 0 RTP/AVP 96\n" +
"a=control:streamid=0\n" +
@@ -135,13 +135,13 @@ func TestResponseWriteStatusAutofill(t *testing.T) {
res := &Response{
StatusCode: StatusMethodNotAllowed,
Header: Header{
"CSeq": []string{"2"},
"Session": []string{"645252166"},
"WWW-Authenticate": []string{
"CSeq": HeaderValue{"2"},
"Session": HeaderValue{"645252166"},
"WWW-Authenticate": HeaderValue{
"Digest realm=\"4419b63f5e51\", nonce=\"8b84a3b789283a8bea8da7fa7d41f08b\", stale=\"FALSE\"",
"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" +