mirror of
https://github.com/aler9/gortsplib
synced 2025-10-07 16:10:59 +08:00
use HeaderValue instead of []string; edit ReadHeaderAuth(), ReadHeaderSession(), ReadHeaderTransport() to accept HeaderValue
This commit is contained in:
60
auth.go
60
auth.go
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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}
|
||||||
}
|
}
|
||||||
|
@@ -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{
|
||||||
|
@@ -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")
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
})
|
})
|
||||||
|
@@ -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.
|
||||||
|
@@ -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)
|
||||||
|
@@ -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"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@@ -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",
|
||||||
|
@@ -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)
|
||||||
|
@@ -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" +
|
||||||
|
Reference in New Issue
Block a user