diff --git a/format/rtspv2/client.go b/format/rtspv2/client.go index 26512f4..0870c69 100644 --- a/format/rtspv2/client.go +++ b/format/rtspv2/client.go @@ -9,6 +9,7 @@ import ( "encoding/binary" "errors" "fmt" + "github.com/deepch/vdk/utils" "html" "io" "log" @@ -136,11 +137,11 @@ func Dial(options RTSPClientOptions) (*RTSPClient, error) { } client.conn = conn client.connRW = bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) - err = client.request(OPTIONS, nil, client.pURL.String(), false, false) + err = client.request(OPTIONS, nil, utils.ParseURLToString(client.pURL), false, false) if err != nil { return nil, err } - err = client.request(DESCRIBE, map[string]string{"Accept": "application/sdp"}, client.pURL.String(), false, false) + err = client.request(DESCRIBE, map[string]string{"Accept": "application/sdp"}, utils.ParseURLToString(client.pURL), false, false) if err != nil { return nil, err } @@ -500,7 +501,7 @@ func (client *RTSPClient) parseURL(rawURL string) error { client.pURL = l client.username = username client.password = password - client.control = l.String() + client.control = utils.ParseURLToString(l) return nil } diff --git a/utils/parse_url.go b/utils/parse_url.go new file mode 100644 index 0000000..2f07d79 --- /dev/null +++ b/utils/parse_url.go @@ -0,0 +1,166 @@ +package utils + +import ( + "fmt" + "net/url" + "strings" +) + +type encoding int + +const ( + encodePath encoding = 1 + iota + encodePathSegment + encodeHost + encodeZone + encodeUserPassword + encodeQueryComponent + encodeFragment +) + +const upperhex = "0123456789ABCDEF" + +func ParseURLToString(u *url.URL) string { + var buf strings.Builder + if u.Scheme != "" { + buf.WriteString(u.Scheme) + buf.WriteByte(':') + } + if u.Opaque != "" { + buf.WriteString(u.Opaque) + } else { + if u.Scheme != "" || u.Host != "" || u.User != nil { + if u.Host != "" || u.Path != "" || u.User != nil { + buf.WriteString("//") + } + if ui := u.User; ui != nil { + username := ui.Username() + password, _ := ui.Password() + buf.WriteString(fmt.Sprintf("%s:%s", username, password)) + buf.WriteByte('@') + } + if h := u.Host; h != "" { + buf.WriteString(escape(h, encodeHost)) + } + } + path := u.EscapedPath() + if path != "" && path[0] != '/' && u.Host != "" { + buf.WriteByte('/') + } + if buf.Len() == 0 { + if i := strings.IndexByte(path, ':'); i > -1 && strings.IndexByte(path[:i], '/') == -1 { + buf.WriteString("./") + } + } + buf.WriteString(path) + } + if u.ForceQuery || u.RawQuery != "" { + buf.WriteByte('?') + buf.WriteString(u.RawQuery) + } + if u.Fragment != "" { + buf.WriteByte('#') + buf.WriteString(u.EscapedFragment()) + } + return buf.String() +} + +func escape(s string, mode encoding) string { + spaceCount, hexCount := 0, 0 + for i := 0; i < len(s); i++ { + c := s[i] + if shouldEscape(c, mode) { + if c == ' ' && mode == encodeQueryComponent { + spaceCount++ + } else { + hexCount++ + } + } + } + + if spaceCount == 0 && hexCount == 0 { + return s + } + + var buf [64]byte + var t []byte + + required := len(s) + 2*hexCount + if required <= len(buf) { + t = buf[:required] + } else { + t = make([]byte, required) + } + + if hexCount == 0 { + copy(t, s) + for i := 0; i < len(s); i++ { + if s[i] == ' ' { + t[i] = '+' + } + } + return string(t) + } + + j := 0 + for i := 0; i < len(s); i++ { + switch c := s[i]; { + case c == ' ' && mode == encodeQueryComponent: + t[j] = '+' + j++ + case shouldEscape(c, mode): + t[j] = '%' + t[j+1] = upperhex[c>>4] + t[j+2] = upperhex[c&15] + j += 3 + default: + t[j] = s[i] + j++ + } + } + return string(t) +} + +func shouldEscape(c byte, mode encoding) bool { + if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' { + return false + } + + if mode == encodeHost || mode == encodeZone { + switch c { + case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '[', ']', '<', '>', '"': + return false + } + } + + switch c { + case '-', '_', '.', '~': + return false + + case '$', '&', '+', ',', '/', ':', ';', '=', '?', '@': + switch mode { + case encodePath: + return c == '?' + + case encodePathSegment: + return c == '/' || c == ';' || c == ',' || c == '?' + + case encodeUserPassword: + return c == '@' || c == '/' || c == '?' || c == ':' + + case encodeQueryComponent: + return true + + case encodeFragment: + return false + } + } + + if mode == encodeFragment { + switch c { + case '!', '(', ')', '*': + return false + } + } + return true +}