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

View File

@@ -125,9 +125,9 @@ func (as *Server) ValidateHeader(v base.HeaderValue, method base.Method, ur *bas
uri := ur.String() uri := ur.String()
if *auth.URI != uri { 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 = ur.Clone()
ur.RemoveControlPath() ur.RemoveControlAttribute()
uri = ur.String() uri = ur.String()
if *auth.URI != uri { if *auth.URI != uri {

View File

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

View File

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

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 { for _, ca := range []struct {
u *URL u *URL
b string b string
@@ -70,14 +70,14 @@ func TestURLBaseControlPath(t *testing.T) {
"trackID=1", "trackID=1",
}, },
} { } {
b, c, ok := ca.u.BaseControlPath() b, c, ok := ca.u.BasePathControlAttr()
require.Equal(t, true, ok) require.Equal(t, true, ok)
require.Equal(t, ca.b, b) require.Equal(t, ca.b, b)
require.Equal(t, ca.c, c) require.Equal(t, ca.c, c)
} }
} }
func TestURLAddControlPath(t *testing.T) { func TestURLAddControlAttribute(t *testing.T) {
for _, ca := range []struct { for _, ca := range []struct {
control string control string
u *URL u *URL
@@ -119,7 +119,7 @@ func TestURLAddControlPath(t *testing.T) {
MustParseURL("rtsp://192.168.1.99:554/test?ctype=video"), 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) 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 // setup authentication
if res.StatusCode == base.StatusUnauthorized && req.URL.User() != nil && c.auth == nil { if res.StatusCode == base.StatusUnauthorized && req.URL.User != nil && c.auth == nil {
auth, err := auth.NewClient(res.Header["WWW-Authenticate"], req.URL.User()) auth, err := auth.NewClient(res.Header["WWW-Authenticate"], req.URL.User)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to setup authentication: %s", err) 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 // copy host and credentials
newUrl.SetHost(baseUrl.Host()) newUrl.Host = baseUrl.Host
newUrl.SetUser(baseUrl.User()) newUrl.User = baseUrl.User
return newUrl return newUrl
} }
// control attribute contains a control path // control attribute contains a control attribute
newUrl := baseUrl.Clone() newUrl := baseUrl.Clone()
newUrl.AddControlPath(control) newUrl.AddControlAttribute(control)
return newUrl return newUrl
} }

View File

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