Files
core/srt/url/url.go
2024-07-09 12:26:02 +02:00

211 lines
3.7 KiB
Go

package url
import (
"fmt"
neturl "net/url"
"regexp"
"strings"
)
type URL struct {
Scheme string
Host string
StreamId string
Options neturl.Values
}
func Parse(srturl string) (*URL, error) {
u, err := neturl.Parse(srturl)
if err != nil {
return nil, err
}
if u.Scheme != "srt" {
return nil, fmt.Errorf("invalid SRT url")
}
options := u.Query()
streamid := options.Get("streamid")
options.Del("streamid")
su := &URL{
Scheme: "srt",
Host: u.Host,
StreamId: streamid,
Options: options,
}
return su, nil
}
func (su *URL) String() string {
options, _ := neturl.ParseQuery(su.Options.Encode())
options.Set("streamid", su.StreamId)
u := neturl.URL{
Scheme: su.Scheme,
Host: su.Host,
RawQuery: options.Encode(),
}
return u.String()
}
func (su *URL) StreamInfo() (*StreamInfo, error) {
s, err := ParseStreamId(su.StreamId)
if err != nil {
return nil, err
}
return &s, nil
}
func (su *URL) SetStreamInfo(si *StreamInfo) {
su.StreamId = si.String()
}
func (su *URL) Hostname() string {
u := neturl.URL{
Host: su.Host,
}
return u.Hostname()
}
func (su *URL) Port() string {
u := neturl.URL{
Host: su.Host,
}
return u.Port()
}
type StreamInfo struct {
Mode string
Resource string
Token string
}
func (si *StreamInfo) String() string {
streamid := si.Resource
if len(si.Mode) != 0 && si.Mode != "request" {
streamid += ",mode:" + si.Mode
}
if len(si.Token) != 0 {
streamid += ",token:" + si.Token
}
return streamid
}
// ParseStreamId parses a streamid. If the streamid is in the old format
// it is detected and parsed accordingly. Otherwise the new simplified
// format will be assumed.
//
// resource[,token:{token}]?[,mode:(publish|*request)]?
//
// If the mode is not provided, "request" will be assumed.
func ParseStreamId(streamid string) (StreamInfo, error) {
si := StreamInfo{Mode: "request"}
if decodedStreamid, err := neturl.QueryUnescape(streamid); err == nil {
streamid = decodedStreamid
}
if strings.HasPrefix(streamid, "#!:") {
return ParseDeprecatedStreamId(streamid)
}
re := regexp.MustCompile(`,(token|mode):(.+)`)
results := map[string]string{}
idEnd := -1
value := streamid
key := ""
for {
matches := re.FindStringSubmatchIndex(value)
if matches == nil {
break
}
if idEnd < 0 {
idEnd = matches[2] - 1
}
if len(key) != 0 {
results[key] = value[:matches[2]-1]
}
key = value[matches[2]:matches[3]]
value = value[matches[4]:matches[5]]
results[key] = value
}
if idEnd < 0 {
idEnd = len(streamid)
}
si.Resource = streamid[:idEnd]
if token, ok := results["token"]; ok {
si.Token = token
}
if mode, ok := results["mode"]; ok {
si.Mode = mode
} else {
si.Mode = "request"
}
return si, nil
}
// ParseDeprecatedStreamId parses a streamid in the old format. The old format
// is based on the recommendation of the SRT specs, but with the special
// character it contains it can cause some trouble in clients (e.g. kiloview
// doesn't like the = character).
func ParseDeprecatedStreamId(streamid string) (StreamInfo, error) {
si := StreamInfo{Mode: "request"}
if !strings.HasPrefix(streamid, "#!:") {
return si, fmt.Errorf("unknown streamid format")
}
streamid = strings.TrimPrefix(streamid, "#!:")
kvs := strings.Split(streamid, ",")
split := func(s, sep string) (string, string, error) {
splitted := strings.SplitN(s, sep, 2)
if len(splitted) != 2 {
return "", "", fmt.Errorf("invalid key/value pair")
}
return splitted[0], splitted[1], nil
}
for _, kv := range kvs {
key, value, err := split(kv, "=")
if err != nil {
continue
}
switch key {
case "m":
si.Mode = value
case "r":
si.Resource = value
case "token":
si.Token = value
default:
}
}
return si, nil
}