base/URL: expose url.URL

This commit is contained in:
aler9
2020-11-03 12:05:33 +01:00
parent 688a39e0e3
commit 300a0e1b1e
7 changed files with 71 additions and 116 deletions

View File

@@ -87,7 +87,7 @@ func NewClient(v base.HeaderValue, userinfo *url.Userinfo) (*Client, error) {
// GenerateHeader generates an Authorization Header that allows to authenticate a request with
// the given method and url.
func (ac *Client) GenerateHeader(method base.Method, ur *base.URL) base.HeaderValue {
ur = ur.CloneWithoutCredentials()
urStr := ur.CloneWithoutCredentials().String()
switch ac.method {
case headers.AuthBasic:
@@ -97,17 +97,14 @@ func (ac *Client) GenerateHeader(method base.Method, ur *base.URL) base.HeaderVa
case headers.AuthDigest:
response := md5Hex(md5Hex(ac.user+":"+ac.realm+":"+ac.pass) + ":" +
ac.nonce + ":" + md5Hex(string(method)+":"+ur.String()))
ac.nonce + ":" + md5Hex(string(method)+":"+urStr))
return (&headers.Auth{
Method: headers.AuthDigest,
Username: &ac.user,
Realm: &ac.realm,
Nonce: &ac.nonce,
URI: func() *string {
v := ur.String()
return &v
}(),
URI: &urStr,
Response: &response,
}).Write()
}

View File

@@ -125,9 +125,9 @@ func (as *Server) ValidateHeader(v base.HeaderValue, method base.Method, ur *bas
uri := ur.String()
if *auth.URI != uri {
// VLC strips the control path; do another try without the control path
// VLC strips the control attribute; do another try without the control attribute
ur = ur.Clone()
ur.RemoveControlPath()
ur.RemoveControlAttribute()
uri = ur.String()
if *auth.URI != uri {

View File

@@ -111,8 +111,8 @@ func (req *Request) Read(rb *bufio.Reader) error {
// Write writes a request.
func (req Request) Write(bw *bufio.Writer) error {
u := req.URL.CloneWithoutCredentials()
_, err := bw.Write([]byte(string(req.Method) + " " + u.String() + " " + rtspProtocol10 + "\r\n"))
urStr := req.URL.CloneWithoutCredentials().String()
_, err := bw.Write([]byte(string(req.Method) + " " + urStr + " " + rtspProtocol10 + "\r\n"))
if err != nil {
return err
}

View File

@@ -16,9 +16,9 @@ func stringsReverseIndexByte(s string, c byte) int {
}
// URL is a RTSP URL.
type URL struct {
inner url.URL
}
// This is basically an HTTP url with some additional functions to handle
// control attributes.
type URL url.URL
// ParseURL parses a RTSP URL.
func ParseURL(s string) (*URL, error) {
@@ -31,7 +31,7 @@ func ParseURL(s string) (*URL, error) {
return nil, fmt.Errorf("wrong scheme")
}
return &URL{*u}, nil
return (*URL)(u), nil
}
// MustParseURL is like ParseURL but panics in case of errors.
@@ -45,54 +45,44 @@ func MustParseURL(s string) *URL {
// String implements fmt.Stringer.
func (u *URL) String() string {
return u.inner.String()
return (*url.URL)(u).String()
}
// Clone clones a URL.
func (u *URL) Clone() *URL {
return &URL{url.URL{
Scheme: u.inner.Scheme,
Opaque: u.inner.Opaque,
User: u.inner.User,
Host: u.inner.Host,
Path: u.inner.Path,
RawPath: u.inner.RawPath,
ForceQuery: u.inner.ForceQuery,
RawQuery: u.inner.RawQuery,
}}
return (*URL)(&url.URL{
Scheme: u.Scheme,
Opaque: u.Opaque,
User: u.User,
Host: u.Host,
Path: u.Path,
RawPath: u.RawPath,
ForceQuery: u.ForceQuery,
RawQuery: u.RawQuery,
})
}
// CloneWithoutCredentials clones a URL without its credentials.
func (u *URL) CloneWithoutCredentials() *URL {
return &URL{url.URL{
Scheme: u.inner.Scheme,
Opaque: u.inner.Opaque,
Host: u.inner.Host,
Path: u.inner.Path,
RawPath: u.inner.RawPath,
ForceQuery: u.inner.ForceQuery,
RawQuery: u.inner.RawQuery,
}}
}
// Host returns the host of a RTSP URL.
func (u *URL) Host() string {
return u.inner.Host
}
// User returns the credentials of a RTSP URL.
func (u *URL) User() *url.Userinfo {
return u.inner.User
return (*URL)(&url.URL{
Scheme: u.Scheme,
Opaque: u.Opaque,
Host: u.Host,
Path: u.Path,
RawPath: u.RawPath,
ForceQuery: u.ForceQuery,
RawQuery: u.RawQuery,
})
}
// BasePath returns the base path of a RTSP URL.
// We assume that the URL doesn't contain a control path.
// We assume that the URL doesn't contain a control attribute.
func (u *URL) BasePath() (string, bool) {
var path string
if u.inner.RawPath != "" {
path = u.inner.RawPath
if u.RawPath != "" {
path = u.RawPath
} else {
path = u.inner.Path
path = u.Path
}
// remove leading slash
@@ -104,17 +94,18 @@ func (u *URL) BasePath() (string, bool) {
return path, true
}
// BaseControlPath returns the base path and the control path of a RTSP URL.
// We assume that the URL contains a control path.
func (u *URL) BaseControlPath() (string, string, bool) {
// BasePathControlAttr returns the base path and the control attribute of a RTSP URL.
// We assume that the URL contains a control attribute.
// We assume that the base path and control attribute are divided with a slash.
func (u *URL) BasePathControlAttr() (string, string, bool) {
var pathAndQuery string
if u.inner.RawPath != "" {
pathAndQuery = u.inner.RawPath
if u.RawPath != "" {
pathAndQuery = u.RawPath
} else {
pathAndQuery = u.inner.Path
pathAndQuery = u.Path
}
if u.inner.RawQuery != "" {
pathAndQuery += "?" + u.inner.RawQuery
if u.RawQuery != "" {
pathAndQuery += "?" + u.RawQuery
}
// remove leading slash
@@ -148,60 +139,27 @@ func (u *URL) BaseControlPath() (string, string, bool) {
return basePath, controlPath, true
}
// SetHost sets the host of a RTSP URL.
func (u *URL) SetHost(host string) {
u.inner.Host = host
}
// SetUser sets the credentials of a RTSP URL.
func (u *URL) SetUser(user *url.Userinfo) {
u.inner.User = user
}
// AddControlPath adds a control path to a RTSP url.
func (u *URL) AddControlPath(controlPath string) {
// special case: query
if controlPath[0] == '?' {
u.inner.RawQuery += controlPath[1:]
return
// AddControlAttribute adds a control attribute to a RTSP url.
func (u *URL) AddControlAttribute(controlPath string) {
if controlPath[0] != '?' {
controlPath = "/" + controlPath
}
// always insert the control path at the end of the url
if u.inner.RawQuery != "" {
if !strings.HasSuffix(u.inner.RawQuery, "/") {
u.inner.RawQuery += "/"
}
u.inner.RawQuery += controlPath
} else {
if u.inner.RawPath != "" {
if !strings.HasSuffix(u.inner.RawPath, "/") {
u.inner.RawPath += "/"
}
u.inner.RawPath += controlPath
}
if !strings.HasSuffix(u.inner.Path, "/") {
u.inner.Path += "/"
}
u.inner.Path += controlPath
}
// insert the control attribute at the end of the url
// if there's a query, insert it after the query
// otherwise insert it after the path
nu, _ := ParseURL(u.String() + controlPath)
*u = *nu
}
// RemoveControlPath removes a control path from an URL.
func (u *URL) RemoveControlPath() {
_, controlPath, ok := u.BaseControlPath()
// RemoveControlAttribute removes a control attribute from an URL.
func (u *URL) RemoveControlAttribute() {
_, controlPath, ok := u.BasePathControlAttr()
if !ok {
return
}
if strings.HasSuffix(u.inner.RawQuery, controlPath) {
u.inner.RawQuery = u.inner.RawQuery[:len(u.inner.RawQuery)-len(controlPath)]
} else if strings.HasSuffix(u.inner.RawPath, controlPath) {
u.inner.RawPath = u.inner.RawPath[:len(u.inner.RawPath)-len(controlPath)]
} else if strings.HasSuffix(u.inner.Path, controlPath) {
u.inner.Path = u.inner.Path[:len(u.inner.Path)-len(controlPath)]
}
urStr := u.String()
nu, _ := ParseURL(urStr[:len(urStr)-len(controlPath)])
*u = *nu
}

View File

@@ -38,7 +38,7 @@ func TestURLBasePath(t *testing.T) {
}
}
func TestURLBaseControlPath(t *testing.T) {
func TestURLBasePathControlAttr(t *testing.T) {
for _, ca := range []struct {
u *URL
b string
@@ -70,14 +70,14 @@ func TestURLBaseControlPath(t *testing.T) {
"trackID=1",
},
} {
b, c, ok := ca.u.BaseControlPath()
b, c, ok := ca.u.BasePathControlAttr()
require.Equal(t, true, ok)
require.Equal(t, ca.b, b)
require.Equal(t, ca.c, c)
}
}
func TestURLAddControlPath(t *testing.T) {
func TestURLAddControlAttribute(t *testing.T) {
for _, ca := range []struct {
control string
u *URL
@@ -119,7 +119,7 @@ func TestURLAddControlPath(t *testing.T) {
MustParseURL("rtsp://192.168.1.99:554/test?ctype=video"),
},
} {
ca.u.AddControlPath(ca.control)
ca.u.AddControlAttribute(ca.control)
require.Equal(t, ca.ou, ca.u)
}
}

View File

@@ -324,8 +324,8 @@ func (c *ConnClient) Do(req *base.Request) (*base.Response, error) {
}
// setup authentication
if res.StatusCode == base.StatusUnauthorized && req.URL.User() != nil && c.auth == nil {
auth, err := auth.NewClient(res.Header["WWW-Authenticate"], req.URL.User())
if res.StatusCode == base.StatusUnauthorized && req.URL.User != nil && c.auth == nil {
auth, err := auth.NewClient(res.Header["WWW-Authenticate"], req.URL.User)
if err != nil {
return nil, fmt.Errorf("unable to setup authentication: %s", err)
}
@@ -440,14 +440,14 @@ func (c *ConnClient) urlForTrack(baseUrl *base.URL, mode TransportMode, track *T
}
// copy host and credentials
newUrl.SetHost(baseUrl.Host())
newUrl.SetUser(baseUrl.User())
newUrl.Host = baseUrl.Host
newUrl.User = baseUrl.User
return newUrl
}
// control attribute contains a control path
// control attribute contains a control attribute
newUrl := baseUrl.Clone()
newUrl.AddControlPath(control)
newUrl.AddControlAttribute(control)
return newUrl
}

View File

@@ -53,7 +53,7 @@ func (d Dialer) DialRead(address string, proto StreamProtocol) (*ConnClient, err
}
conn, err := NewConnClient(ConnClientConf{
Host: u.Host(),
Host: u.Host,
ReadTimeout: d.ReadTimeout,
WriteTimeout: d.WriteTimeout,
ReadBufferCount: d.ReadBufferCount,
@@ -117,7 +117,7 @@ func (d Dialer) DialPublish(address string, proto StreamProtocol, tracks Tracks)
}
conn, err := NewConnClient(ConnClientConf{
Host: u.Host(),
Host: u.Host,
ReadTimeout: d.ReadTimeout,
WriteTimeout: d.WriteTimeout,
ReadBufferCount: d.ReadBufferCount,