HeaderAuth: use struct instead of map for storing

This commit is contained in:
aler9
2020-09-13 16:40:12 +02:00
parent 633f25bb33
commit 45cf5562de
4 changed files with 304 additions and 147 deletions

73
auth.go
View File

@@ -64,19 +64,15 @@ func (as *AuthServer) GenerateHeader() HeaderValue {
switch m { switch m {
case Basic: case Basic:
ret = append(ret, (&HeaderAuth{ ret = append(ret, (&HeaderAuth{
Prefix: "Basic", Method: Basic,
Values: map[string]string{ Realm: &as.realm,
"realm": as.realm,
},
}).Write()...) }).Write()...)
case Digest: case Digest:
ret = append(ret, (&HeaderAuth{ ret = append(ret, (&HeaderAuth{
Prefix: "Digest", Method: Digest,
Values: map[string]string{ Realm: &as.realm,
"realm": as.realm, Nonce: &as.nonce,
"nonce": as.nonce,
},
}).Write()...) }).Write()...)
} }
} }
@@ -110,46 +106,41 @@ func (as *AuthServer) ValidateHeader(v HeaderValue, method Method, ur *url.URL)
return err return err
} }
inRealm, ok := auth.Values["realm"] if auth.Realm == nil {
if !ok {
return fmt.Errorf("realm not provided") return fmt.Errorf("realm not provided")
} }
inNonce, ok := auth.Values["nonce"] if auth.Nonce == nil {
if !ok {
return fmt.Errorf("nonce not provided") return fmt.Errorf("nonce not provided")
} }
inUsername, ok := auth.Values["username"] if auth.Username == nil {
if !ok {
return fmt.Errorf("username not provided") return fmt.Errorf("username not provided")
} }
inUri, ok := auth.Values["uri"] if auth.URI == nil {
if !ok {
return fmt.Errorf("uri not provided") return fmt.Errorf("uri not provided")
} }
inResponse, ok := auth.Values["response"] if auth.Response == nil {
if !ok {
return fmt.Errorf("response not provided") return fmt.Errorf("response not provided")
} }
if inNonce != as.nonce { if *auth.Nonce != as.nonce {
return fmt.Errorf("wrong nonce") return fmt.Errorf("wrong nonce")
} }
if inRealm != as.realm { if *auth.Realm != as.realm {
return fmt.Errorf("wrong realm") return fmt.Errorf("wrong realm")
} }
if inUsername != as.user { if *auth.Username != as.user {
return fmt.Errorf("wrong username") return fmt.Errorf("wrong username")
} }
uri := ur.String() uri := ur.String()
if inUri != uri { if *auth.URI != uri {
// VLC strips the subpath // VLC strips the subpath
newUrl := *ur newUrl := *ur
newUrl.Path = func() string { newUrl.Path = func() string {
@@ -163,7 +154,7 @@ func (as *AuthServer) ValidateHeader(v HeaderValue, method Method, ur *url.URL)
}() }()
uri = newUrl.String() uri = newUrl.String()
if inUri != uri { if *auth.URI != uri {
return fmt.Errorf("wrong url") return fmt.Errorf("wrong url")
} }
} }
@@ -171,7 +162,7 @@ func (as *AuthServer) ValidateHeader(v HeaderValue, method Method, ur *url.URL)
response := md5Hex(md5Hex(as.user+":"+as.realm+":"+as.pass) + response := md5Hex(md5Hex(as.user+":"+as.realm+":"+as.pass) +
":" + as.nonce + ":" + md5Hex(string(method)+":"+uri)) ":" + as.nonce + ":" + md5Hex(string(method)+":"+uri))
if inResponse != response { if *auth.Response != response {
return fmt.Errorf("wrong response") return fmt.Errorf("wrong response")
} }
@@ -209,13 +200,11 @@ func newAuthClient(v HeaderValue, user string, pass string) (*authClient, error)
return nil, err return nil, err
} }
realm, ok := auth.Values["realm"] if auth.Realm == nil {
if !ok {
return nil, fmt.Errorf("realm not provided") return nil, fmt.Errorf("realm not provided")
} }
nonce, ok := auth.Values["nonce"] if auth.Nonce == nil {
if !ok {
return nil, fmt.Errorf("nonce not provided") return nil, fmt.Errorf("nonce not provided")
} }
@@ -223,8 +212,8 @@ func newAuthClient(v HeaderValue, user string, pass string) (*authClient, error)
user: user, user: user,
pass: pass, pass: pass,
method: Digest, method: Digest,
realm: realm, realm: *auth.Realm,
nonce: nonce, nonce: *auth.Nonce,
}, nil }, nil
} }
@@ -241,8 +230,7 @@ func newAuthClient(v HeaderValue, user string, pass string) (*authClient, error)
return nil, err return nil, err
} }
realm, ok := auth.Values["realm"] if auth.Realm == nil {
if !ok {
return nil, fmt.Errorf("realm not provided") return nil, fmt.Errorf("realm not provided")
} }
@@ -250,7 +238,7 @@ func newAuthClient(v HeaderValue, user string, pass string) (*authClient, error)
user: user, user: user,
pass: pass, pass: pass,
method: Basic, method: Basic,
realm: realm, realm: *auth.Realm,
}, nil }, nil
} }
@@ -271,14 +259,15 @@ func (ac *authClient) GenerateHeader(method Method, ur *url.URL) HeaderValue {
ac.nonce + ":" + md5Hex(string(method)+":"+ur.String())) ac.nonce + ":" + md5Hex(string(method)+":"+ur.String()))
return (&HeaderAuth{ return (&HeaderAuth{
Prefix: "Digest", Method: Digest,
Values: map[string]string{ Username: &ac.user,
"username": ac.user, Realm: &ac.realm,
"realm": ac.realm, Nonce: &ac.nonce,
"nonce": ac.nonce, URI: func() *string {
"uri": ur.String(), v := ur.String()
"response": response, return &v
}, }(),
Response: &response,
}).Write() }).Write()
} }

View File

@@ -2,18 +2,68 @@ package gortsplib
import ( import (
"fmt" "fmt"
"regexp"
"sort"
"strings" "strings"
) )
// HeaderAuth is an Authenticate or a WWWW-Authenticate header. // HeaderAuth is an Authenticate or a WWWW-Authenticate header.
type HeaderAuth struct { type HeaderAuth struct {
Prefix string // authentication method
Values map[string]string Method AuthMethod
// (optional) username
Username *string
// (optional) realm
Realm *string
// (optional) nonce
Nonce *string
// (optional) uri
URI *string
// (optional) response
Response *string
// (optional) opaque
Opaque *string
// (optional) stale
Stale *string
// (optional) algorithm
Algorithm *string
} }
var regHeaderAuthKeyValue = regexp.MustCompile("^([a-z]+)=(\"(.*?)\"|([a-zA-Z0-9]+))(, *|$)") func findValue(v0 string) (string, string, error) {
if v0 == "" {
return "", "", nil
}
if v0[0] == '"' {
i := 1
for {
if i >= len(v0) {
return "", "", fmt.Errorf("apices not closed (%v)", v0)
}
if v0[i] == '"' {
return v0[1:i], v0[i+1:], nil
}
i++
}
}
i := 0
for {
if i >= len(v0) || v0[i] == ',' {
return v0[:i], v0[i:], nil
}
i++
}
}
// ReadHeaderAuth parses an Authenticate or a WWW-Authenticate header. // ReadHeaderAuth parses an Authenticate or a WWW-Authenticate header.
func ReadHeaderAuth(v HeaderValue) (*HeaderAuth, error) { func ReadHeaderAuth(v HeaderValue) (*HeaderAuth, error) {
@@ -25,28 +75,79 @@ func ReadHeaderAuth(v HeaderValue) (*HeaderAuth, error) {
return nil, fmt.Errorf("value provided multiple times (%v)", v) return nil, fmt.Errorf("value provided multiple times (%v)", v)
} }
ha := &HeaderAuth{ ha := &HeaderAuth{}
Values: make(map[string]string),
}
v0 := v[0] v0 := v[0]
i := strings.IndexByte(v[0], ' ') i := strings.IndexByte(v0, ' ')
if i < 0 { if i < 0 {
return nil, fmt.Errorf("unable to find prefix (%s)", v0) return nil, fmt.Errorf("unable to find method (%s)", v0)
} }
ha.Prefix, v0 = v0[:i], v0[i+1:]
switch v0[:i] {
case "Basic":
ha.Method = Basic
case "Digest":
ha.Method = Digest
default:
return nil, fmt.Errorf("invalid method (%s)", v0[:i])
}
v0 = v0[i+1:]
for len(v0) > 0 { for len(v0) > 0 {
m := regHeaderAuthKeyValue.FindStringSubmatch(v0) i := strings.IndexByte(v0, '=')
if m == nil { if i < 0 {
return nil, fmt.Errorf("unable to parse key-value (%s)", v0) return nil, fmt.Errorf("unable to find key (%s)", v0)
} }
v0 = v0[len(m[0]):] var key string
key, v0 = v0[:i], v0[i+1:]
m[2] = strings.TrimPrefix(m[2], "\"") var val string
m[2] = strings.TrimSuffix(m[2], "\"") var err error
ha.Values[m[1]] = m[2] val, v0, err = findValue(v0)
if err != nil {
return nil, err
}
switch key {
case "username":
ha.Username = &val
case "realm":
ha.Realm = &val
case "nonce":
ha.Nonce = &val
case "uri":
ha.URI = &val
case "response":
ha.Response = &val
case "opaque":
ha.Opaque = &val
case "stale":
ha.Stale = &val
case "algorithm":
ha.Algorithm = &val
// ignore non-standard keys
}
// skip comma
if len(v0) > 0 && v0[0] == ',' {
v0 = v0[1:]
}
// skip spaces
for len(v0) > 0 && v0[0] == ' ' {
v0 = v0[1:]
}
} }
return ha, nil return ha, nil
@@ -54,48 +155,53 @@ func ReadHeaderAuth(v HeaderValue) (*HeaderAuth, error) {
// Write encodes an Authenticate or a WWW-Authenticate header. // Write encodes an Authenticate or a WWW-Authenticate header.
func (ha *HeaderAuth) Write() HeaderValue { func (ha *HeaderAuth) Write() HeaderValue {
ret := ha.Prefix + " " ret := ""
// follow a specific order, otherwise some clients/servers do not work correctly switch ha.Method {
var sortedKeys []string case Basic:
for key := range ha.Values { ret += "Basic"
sortedKeys = append(sortedKeys, key)
}
score := func(v string) int {
switch v {
case "username":
return 0
case "realm":
return 1
case "nonce":
return 2
case "uri":
return 3
case "response":
return 4
case "opaque":
return 5
case "stale":
return 6
case "algorithm":
return 7
}
return 8
}
sort.Slice(sortedKeys, func(a, b int) bool {
sa := score(sortedKeys[a])
sb := score(sortedKeys[b])
if sa != sb {
return sa < sb
}
return a < b
})
var tmp []string case Digest:
for _, key := range sortedKeys { ret += "Digest"
tmp = append(tmp, key+"=\""+ha.Values[key]+"\"")
} }
ret += strings.Join(tmp, ", ")
ret += " "
var vals []string
if ha.Username != nil {
vals = append(vals, "username=\""+*ha.Username+"\"")
}
if ha.Realm != nil {
vals = append(vals, "realm=\""+*ha.Realm+"\"")
}
if ha.Nonce != nil {
vals = append(vals, "nonce=\""+*ha.Nonce+"\"")
}
if ha.URI != nil {
vals = append(vals, "uri=\""+*ha.URI+"\"")
}
if ha.Response != nil {
vals = append(vals, "response=\""+*ha.Response+"\"")
}
if ha.Opaque != nil {
vals = append(vals, "opaque=\""+*ha.Opaque+"\"")
}
if ha.Stale != nil {
vals = append(vals, "stale=\""+*ha.Stale+"\"")
}
if ha.Algorithm != nil {
vals = append(vals, "algorithm=\""+*ha.Algorithm+"\"")
}
ret += strings.Join(vals, ", ")
return HeaderValue{ret} return HeaderValue{ret}
} }

View File

@@ -17,10 +17,11 @@ var casesHeaderAuth = []struct {
HeaderValue{`Basic realm="4419b63f5e51"`}, HeaderValue{`Basic realm="4419b63f5e51"`},
HeaderValue{`Basic realm="4419b63f5e51"`}, HeaderValue{`Basic realm="4419b63f5e51"`},
&HeaderAuth{ &HeaderAuth{
Prefix: "Basic", Method: Basic,
Values: map[string]string{ Realm: func() *string {
"realm": "4419b63f5e51", v := "4419b63f5e51"
}, return &v
}(),
}, },
}, },
{ {
@@ -28,12 +29,19 @@ var casesHeaderAuth = []struct {
HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`}, HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`},
HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`}, HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`},
&HeaderAuth{ &HeaderAuth{
Prefix: "Digest", Method: Digest,
Values: map[string]string{ Realm: func() *string {
"realm": "4419b63f5e51", v := "4419b63f5e51"
"nonce": "8b84a3b789283a8bea8da7fa7d41f08b", return &v
"stale": "FALSE", }(),
}, Nonce: func() *string {
v := "8b84a3b789283a8bea8da7fa7d41f08b"
return &v
}(),
Stale: func() *string {
v := "FALSE"
return &v
}(),
}, },
}, },
{ {
@@ -41,12 +49,19 @@ var casesHeaderAuth = []struct {
HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale=FALSE`}, HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale=FALSE`},
HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`}, HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`},
&HeaderAuth{ &HeaderAuth{
Prefix: "Digest", Method: Digest,
Values: map[string]string{ Realm: func() *string {
"realm": "4419b63f5e51", v := "4419b63f5e51"
"nonce": "8b84a3b789283a8bea8da7fa7d41f08b", return &v
"stale": "FALSE", }(),
}, Nonce: func() *string {
v := "8b84a3b789283a8bea8da7fa7d41f08b"
return &v
}(),
Stale: func() *string {
v := "FALSE"
return &v
}(),
}, },
}, },
{ {
@@ -54,12 +69,19 @@ var casesHeaderAuth = []struct {
HeaderValue{`Digest realm="4419b63f5e51",nonce="133767111917411116111311118211673010032", stale="FALSE"`}, HeaderValue{`Digest realm="4419b63f5e51",nonce="133767111917411116111311118211673010032", stale="FALSE"`},
HeaderValue{`Digest realm="4419b63f5e51", nonce="133767111917411116111311118211673010032", stale="FALSE"`}, HeaderValue{`Digest realm="4419b63f5e51", nonce="133767111917411116111311118211673010032", stale="FALSE"`},
&HeaderAuth{ &HeaderAuth{
Prefix: "Digest", Method: Digest,
Values: map[string]string{ Realm: func() *string {
"realm": "4419b63f5e51", v := "4419b63f5e51"
"nonce": "133767111917411116111311118211673010032", return &v
"stale": "FALSE", }(),
}, Nonce: func() *string {
v := "133767111917411116111311118211673010032"
return &v
}(),
Stale: func() *string {
v := "FALSE"
return &v
}(),
}, },
}, },
{ {
@@ -67,14 +89,27 @@ var casesHeaderAuth = []struct {
HeaderValue{`Digest username="aa", realm="bb", nonce="cc", uri="dd", response="ee"`}, HeaderValue{`Digest username="aa", realm="bb", nonce="cc", uri="dd", response="ee"`},
HeaderValue{`Digest username="aa", realm="bb", nonce="cc", uri="dd", response="ee"`}, HeaderValue{`Digest username="aa", realm="bb", nonce="cc", uri="dd", response="ee"`},
&HeaderAuth{ &HeaderAuth{
Prefix: "Digest", Method: Digest,
Values: map[string]string{ Username: func() *string {
"username": "aa", v := "aa"
"realm": "bb", return &v
"nonce": "cc", }(),
"uri": "dd", Realm: func() *string {
"response": "ee", v := "bb"
}, return &v
}(),
Nonce: func() *string {
v := "cc"
return &v
}(),
URI: func() *string {
v := "dd"
return &v
}(),
Response: func() *string {
v := "ee"
return &v
}(),
}, },
}, },
{ {
@@ -82,14 +117,27 @@ var casesHeaderAuth = []struct {
HeaderValue{`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"`},
HeaderValue{`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"`},
&HeaderAuth{ &HeaderAuth{
Prefix: "Digest", Method: Digest,
Values: map[string]string{ Username: func() *string {
"username": "", v := ""
"realm": "IPCAM", return &v
"nonce": "5d17cd12b9fa8a85ac5ceef0926ea5a6", }(),
"uri": "rtsp://localhost:8554/mystream", Realm: func() *string {
"response": "c072ae90eb4a27f4cdcb90d62266b2a1", v := "IPCAM"
}, return &v
}(),
Nonce: func() *string {
v := "5d17cd12b9fa8a85ac5ceef0926ea5a6"
return &v
}(),
URI: func() *string {
v := "rtsp://localhost:8554/mystream"
return &v
}(),
Response: func() *string {
v := "c072ae90eb4a27f4cdcb90d62266b2a1"
return &v
}(),
}, },
}, },
{ {
@@ -97,14 +145,27 @@ var casesHeaderAuth = []struct {
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",nonce="752a62306daf32b401a41004555c7663",opaque="",stale=FALSE,algorithm=MD5`},
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", nonce="752a62306daf32b401a41004555c7663", opaque="", stale="FALSE", algorithm="MD5"`},
&HeaderAuth{ &HeaderAuth{
Prefix: "Digest", Method: Digest,
Values: map[string]string{ Realm: func() *string {
"realm": "Please log in with a valid username", v := "Please log in with a valid username"
"nonce": "752a62306daf32b401a41004555c7663", return &v
"opaque": "", }(),
"stale": "FALSE", Nonce: func() *string {
"algorithm": "MD5", v := "752a62306daf32b401a41004555c7663"
}, return &v
}(),
Opaque: func() *string {
v := ""
return &v
}(),
Stale: func() *string {
v := "FALSE"
return &v
}(),
Algorithm: func() *string {
v := "MD5"
return &v
}(),
}, },
}, },
} }

View File

@@ -75,15 +75,14 @@ func ReadHeaderTransport(v HeaderValue) (*HeaderTransport, error) {
switch parts[0] { switch parts[0] {
case "RTP/AVP", "RTP/AVP/UDP": case "RTP/AVP", "RTP/AVP/UDP":
ht.Protocol = StreamProtocolUDP ht.Protocol = StreamProtocolUDP
parts = parts[1:]
case "RTP/AVP/TCP": case "RTP/AVP/TCP":
ht.Protocol = StreamProtocolTCP ht.Protocol = StreamProtocolTCP
parts = parts[1:]
default: default:
return nil, fmt.Errorf("invalid protocol (%v)", v) return nil, fmt.Errorf("invalid protocol (%v)", v)
} }
parts = parts[1:]
switch parts[0] { switch parts[0] {
case "unicast": case "unicast":
@@ -144,6 +143,8 @@ func ReadHeaderTransport(v HeaderValue) (*HeaderTransport, error) {
v = strings.TrimSuffix(v, "\"") v = strings.TrimSuffix(v, "\"")
ht.Mode = &v ht.Mode = &v
} }
// ignore non-standard keys
} }
return ht, nil return ht, nil