From d85cfae2ed507c0836a1da9146b2e63cf7d0bd75 Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Sat, 31 Oct 2020 14:56:31 +0100 Subject: [PATCH] add URL utils --- base/request_test.go | 14 ++--- base/url.go | 88 ++++++++++++++++++++++++++++ base/url_test.go | 109 +++++++++++++++++++++++++++++++++++ connclient.go | 21 +------ rtcpreceiver/rtcpreceiver.go | 2 +- 5 files changed, 206 insertions(+), 28 deletions(-) create mode 100644 base/url.go create mode 100644 base/url_test.go diff --git a/base/request_test.go b/base/request_test.go index c1e4fec4..51ca2a78 100644 --- a/base/request_test.go +++ b/base/request_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" ) -func mustParseUrl(s string) *url.URL { +func urlMustParse(s string) *url.URL { u, err := url.Parse(s) if err != nil { panic(err) @@ -31,7 +31,7 @@ var casesRequest = []struct { "\r\n"), Request{ Method: "OPTIONS", - Url: mustParseUrl("rtsp://example.com/media.mp4"), + Url: urlMustParse("rtsp://example.com/media.mp4"), Header: Header{ "CSeq": HeaderValue{"1"}, "Require": HeaderValue{"implicit-play"}, @@ -47,7 +47,7 @@ var casesRequest = []struct { "\r\n"), Request{ Method: "DESCRIBE", - Url: mustParseUrl("rtsp://example.com/media.mp4"), + Url: urlMustParse("rtsp://example.com/media.mp4"), Header: Header{ "Accept": HeaderValue{"application/sdp"}, "CSeq": HeaderValue{"2"}, @@ -55,14 +55,14 @@ var casesRequest = []struct { }, }, { - "describe with exclamation mark", + "describe with special chars", []byte("DESCRIBE rtsp://192.168.1.99:554/user=tmp&password=BagRep1!&channel=1&stream=0.sdp RTSP/1.0\r\n" + "Accept: application/sdp\r\n" + "CSeq: 3\r\n" + "\r\n"), Request{ Method: "DESCRIBE", - Url: mustParseUrl("rtsp://192.168.1.99:554/user=tmp&password=BagRep1!&channel=1&stream=0.sdp"), + Url: urlMustParse("rtsp://192.168.1.99:554/user=tmp&password=BagRep1!&channel=1&stream=0.sdp"), Header: Header{ "Accept": HeaderValue{"application/sdp"}, "CSeq": HeaderValue{"3"}, @@ -91,7 +91,7 @@ var casesRequest = []struct { "m=video 2232 RTP/AVP 31\n"), Request{ Method: "ANNOUNCE", - Url: mustParseUrl("rtsp://example.com/media.mp4"), + Url: urlMustParse("rtsp://example.com/media.mp4"), Header: Header{ "CSeq": HeaderValue{"7"}, "Date": HeaderValue{"23 Jan 1997 15:35:06 GMT"}, @@ -125,7 +125,7 @@ var casesRequest = []struct { "jitter\n"), Request{ Method: "GET_PARAMETER", - Url: mustParseUrl("rtsp://example.com/media.mp4"), + Url: urlMustParse("rtsp://example.com/media.mp4"), Header: Header{ "CSeq": HeaderValue{"9"}, "Content-Type": HeaderValue{"text/parameters"}, diff --git a/base/url.go b/base/url.go new file mode 100644 index 00000000..85ad181a --- /dev/null +++ b/base/url.go @@ -0,0 +1,88 @@ +package base + +import ( + "strings" + "net/url" +) + +func stringsReverseIndexByte(s string, c byte) int { + for i := len(s) - 1; i >= 0; i-- { + if s[i] == c { + return i + } + } + return -1 +} + +// URLGetBasePath returns the base path of a RTSP URL. +// We assume that the URL doesn't contain a control path. +func URLGetBasePath(u *url.URL) string { + if u.RawPath != "" { + return u.RawPath[1:] // remove leading slash + } + return u.Path[1:] // remove leading slash +} + +// URLGetBaseControlPath returns the base path and the control path of a RTSP URL. +// We assume that the URL contains a control path. +func URLGetBaseControlPath(u *url.URL) (string, string, bool) { + pathAndQuery := "" + if u.RawPath != "" { + pathAndQuery += u.RawPath + } else { + pathAndQuery += u.Path + } + if u.RawQuery != "" { + pathAndQuery += "?" + u.RawQuery + } + + pathAndQuery = pathAndQuery[1:] // remove leading slash + + pos := stringsReverseIndexByte(pathAndQuery, '/') + if pos < 0 { + return "", "", false + } + + basePath := pathAndQuery[:pos] + + // remove query from basePath + i := strings.IndexByte(basePath, '?') + if i >= 0 { + basePath = basePath[:i] + } + + if len(basePath) == 0 { + return "", "", false + } + + controlPath := pathAndQuery[pos+1:] + if len(controlPath) == 0 { + return "", "", false + } + + return basePath, controlPath, true +} + +// URLAddControlPath adds a control path to a RTSP url. +func URLAddControlPath(u *url.URL, controlPath string) { + // always insert the control path at the end of the url + if u.RawQuery != "" { + if !strings.HasSuffix(u.RawQuery, "/") { + u.RawQuery += "/" + } + u.RawQuery += controlPath + + } else { + if u.RawPath != "" { + if !strings.HasSuffix(u.RawPath, "/") { + u.RawPath += "/" + } + u.RawPath += controlPath + } + + if !strings.HasSuffix(u.Path, "/") { + u.Path += "/" + } + u.Path += controlPath + } +} diff --git a/base/url_test.go b/base/url_test.go new file mode 100644 index 00000000..9e6cc919 --- /dev/null +++ b/base/url_test.go @@ -0,0 +1,109 @@ +package base + +import ( + "net/url" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestURLGetBasePath(t *testing.T) { + for _, ca := range []struct{ + u *url.URL + b string + } { + { + urlMustParse("rtsp://localhost:8554/teststream"), + "teststream", + }, + { + urlMustParse("rtsp://localhost:8554/test/stream"), + "test/stream", + }, + { + urlMustParse("rtsp://192.168.1.99:554/test?user=tmp&password=BagRep1&channel=1&stream=0.sdp"), + "test", + }, + { + urlMustParse("rtsp://192.168.1.99:554/te!st?user=tmp&password=BagRep1!&channel=1&stream=0.sdp"), + "te!st", + }, + { + urlMustParse("rtsp://192.168.1.99:554/user=tmp&password=BagRep1!&channel=1&stream=0.sdp"), + "user=tmp&password=BagRep1!&channel=1&stream=0.sdp", + }, + } { + b := URLGetBasePath(ca.u) + require.Equal(t, ca.b, b) + } +} + +func TestURLGetBaseControlPath(t *testing.T) { + for _, ca := range []struct{ + u *url.URL + b string + c string + } { + { + urlMustParse("rtsp://localhost:8554/teststream/trackID=1"), + "teststream", + "trackID=1", + }, + { + urlMustParse("rtsp://localhost:8554/test/stream/trackID=1"), + "test/stream", + "trackID=1", + }, + { + urlMustParse("rtsp://192.168.1.99:554/test?user=tmp&password=BagRep1&channel=1&stream=0.sdp/trackID=1"), + "test", + "trackID=1", + }, + { + urlMustParse("rtsp://192.168.1.99:554/te!st?user=tmp&password=BagRep1!&channel=1&stream=0.sdp/trackID=1"), + "te!st", + "trackID=1", + }, + { + urlMustParse("rtsp://192.168.1.99:554/user=tmp&password=BagRep1!&channel=1&stream=0.sdp/trackID=1"), + "user=tmp&password=BagRep1!&channel=1&stream=0.sdp", + "trackID=1", + }, + } { + b, c, ok := URLGetBaseControlPath(ca.u) + require.Equal(t, true, ok) + require.Equal(t, ca.b, b) + require.Equal(t, ca.c, c) + } +} + +func TestURLAddControlPath(t *testing.T) { + for _, ca := range []struct{ + u *url.URL + ou *url.URL + } { + { + urlMustParse("rtsp://localhost:8554/teststream"), + urlMustParse("rtsp://localhost:8554/teststream/trackID=1"), + }, + { + urlMustParse("rtsp://localhost:8554/test/stream"), + urlMustParse("rtsp://localhost:8554/test/stream/trackID=1"), + }, + { + urlMustParse("rtsp://192.168.1.99:554/test?user=tmp&password=BagRep1&channel=1&stream=0.sdp"), + urlMustParse("rtsp://192.168.1.99:554/test?user=tmp&password=BagRep1&channel=1&stream=0.sdp/trackID=1"), + }, + { + urlMustParse("rtsp://192.168.1.99:554/te!st?user=tmp&password=BagRep1!&channel=1&stream=0.sdp"), + urlMustParse("rtsp://192.168.1.99:554/te!st?user=tmp&password=BagRep1!&channel=1&stream=0.sdp/trackID=1"), + }, + { + urlMustParse("rtsp://192.168.1.99:554/user=tmp&password=BagRep1!&channel=1&stream=0.sdp"), + urlMustParse("rtsp://192.168.1.99:554/user=tmp&password=BagRep1!&channel=1&stream=0.sdp/trackID=1"), + }, + } { + URLAddControlPath(ca.u, "trackID=1") + require.Equal(t, ca.ou, ca.u) + } +} diff --git a/connclient.go b/connclient.go index 9a8a5f5b..0e425161 100644 --- a/connclient.go +++ b/connclient.go @@ -464,26 +464,7 @@ func (c *ConnClient) urlForTrack(baseUrl *url.URL, mode TransportMode, track *Tr RawPath: baseUrl.RawPath, RawQuery: baseUrl.RawQuery, } - - // insert the control at the end of the url - if u.RawQuery != "" { - if !strings.HasSuffix(u.RawQuery, "/") { - u.RawQuery += "/" - } - u.RawQuery += control - - } else if u.RawPath != "" { - if !strings.HasSuffix(u.RawPath, "/") { - u.RawPath += "/" - } - u.RawPath += control - - } else { - if !strings.HasSuffix(u.Path, "/") { - u.Path += "/" - } - u.Path += control - } + base.URLAddControlPath(u, control) return u } diff --git a/rtcpreceiver/rtcpreceiver.go b/rtcpreceiver/rtcpreceiver.go index 5b6018ae..ce34f0ec 100644 --- a/rtcpreceiver/rtcpreceiver.go +++ b/rtcpreceiver/rtcpreceiver.go @@ -23,7 +23,7 @@ type reportReq struct { res chan []byte } -// RtcpReceiver is an object that helps building RTCP receiver reports, by parsing +// RtcpReceiver allows building RTCP receiver reports, by parsing // incoming frames. type RtcpReceiver struct { mutex sync.Mutex