You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lal/pkg/base/url.go

205 lines
4.7 KiB
Go

// Copyright 2020, Chef. All rights reserved.
// https://github.com/q191201771/lal
//
// Use of this source code is governed by a MIT-style license
// that can be found in the License file.
//
// Author: Chef (191201771@qq.com)
package base
import (
"errors"
"fmt"
"net"
"net/url"
"strconv"
"strings"
)
// 见单元测试
// TODO chef: 考虑部分内容移入naza中
var ErrUrl = errors.New("lal.url: fxxk")
const (
DefaultRtmpPort = 1935
DefaultHttpPort = 80
DefaultHttpsPort = 443
DefaultRtspPort = 554
)
type UrlPathContext struct {
PathWithRawQuery string
Path string
PathWithoutLastItem string // 注意,没有前面的'/',也没有后面的'/'
LastItemOfPath string // 注意,没有前面的'/'
RawQuery string
}
// TODO chef: 考虑把rawUrl也放入其中
type UrlContext struct {
Url string
Scheme string
Username string
Password string
StdHost string // host or host:port
HostWithPort string
Host string
Port int
//UrlPathContext
PathWithRawQuery string
Path string
PathWithoutLastItem string // 注意,没有前面的'/',也没有后面的'/'
LastItemOfPath string // 注意,没有前面的'/'
RawQuery string
RawUrlWithoutUserInfo string
}
func ParseUrl(rawUrl string, defaultPort int) (ctx UrlContext, err error) {
ctx.Url = rawUrl
stdUrl, err := url.Parse(rawUrl)
if err != nil {
return ctx, err
}
if stdUrl.Scheme == "" {
return ctx, ErrUrl
}
ctx.Scheme = stdUrl.Scheme
ctx.StdHost = stdUrl.Host
ctx.Username = stdUrl.User.Username()
ctx.Password, _ = stdUrl.User.Password()
h, p, err := net.SplitHostPort(stdUrl.Host)
if err != nil {
// url中端口不存r
ctx.Host = stdUrl.Host
if defaultPort == -1 {
ctx.HostWithPort = stdUrl.Host
} else {
ctx.HostWithPort = net.JoinHostPort(stdUrl.Host, fmt.Sprintf("%d", defaultPort))
ctx.Port = defaultPort
}
} else {
// 端口存在
ctx.Port, err = strconv.Atoi(p)
if err != nil {
return ctx, err
}
ctx.Host = h
ctx.HostWithPort = stdUrl.Host
}
pathCtx, err := parseUrlPath(stdUrl)
if err != nil {
return ctx, err
}
ctx.PathWithRawQuery = pathCtx.PathWithRawQuery
ctx.Path = pathCtx.Path
ctx.PathWithoutLastItem = pathCtx.PathWithoutLastItem
ctx.LastItemOfPath = pathCtx.LastItemOfPath
ctx.RawQuery = pathCtx.RawQuery
ctx.RawUrlWithoutUserInfo = fmt.Sprintf("%s://%s%s", ctx.Scheme, ctx.StdHost, ctx.PathWithRawQuery)
return ctx, nil
}
func ParseRtmpUrl(rawUrl string) (ctx UrlContext, err error) {
ctx, err = ParseUrl(rawUrl, DefaultRtmpPort)
if err != nil {
return
}
if ctx.Scheme != "rtmp" || ctx.Host == "" || ctx.Path == "" {
return ctx, ErrUrl
}
// 注意使用ffmpeg推流时会把`rtmp://127.0.0.1/test110`中的test110作为appName(streamName则为空)
// 这种其实已不算十分合法的rtmp url了
// 我们这里也处理一下和ffmpeg保持一致
if ctx.PathWithoutLastItem == "" && ctx.LastItemOfPath != "" {
tmp := ctx.PathWithoutLastItem
ctx.PathWithoutLastItem = ctx.LastItemOfPath
ctx.LastItemOfPath = tmp
}
return
}
func ParseHttpflvUrl(rawUrl string, isHttps bool) (ctx UrlContext, err error) {
return ParseHttpUrl(rawUrl, isHttps, ".flv")
}
func ParseHttptsUrl(rawUrl string, isHttps bool) (ctx UrlContext, err error) {
return ParseHttpUrl(rawUrl, isHttps, ".ts")
}
func ParseRtspUrl(rawUrl string) (ctx UrlContext, err error) {
ctx, err = ParseUrl(rawUrl, DefaultRtspPort)
if err != nil {
return
}
if ctx.Scheme != "rtsp" || ctx.Host == "" || ctx.Path == "" {
return ctx, ErrUrl
}
return
}
func parseUrlPath(stdUrl *url.URL) (ctx UrlPathContext, err error) {
ctx.Path = stdUrl.Path
index := strings.LastIndexByte(ctx.Path, '/')
if index == -1 {
ctx.PathWithoutLastItem = ""
ctx.LastItemOfPath = ""
} else if index == 0 {
if ctx.Path == "/" {
ctx.PathWithoutLastItem = ""
ctx.LastItemOfPath = ""
} else {
ctx.PathWithoutLastItem = ""
ctx.LastItemOfPath = ctx.Path[1:]
}
} else {
ctx.PathWithoutLastItem = ctx.Path[1:index]
ctx.LastItemOfPath = ctx.Path[index+1:]
}
ctx.RawQuery = stdUrl.RawQuery
if ctx.RawQuery == "" {
ctx.PathWithRawQuery = ctx.Path
} else {
ctx.PathWithRawQuery = fmt.Sprintf("%s?%s", ctx.Path, ctx.RawQuery)
}
return ctx, nil
}
func ParseHttpUrl(rawUrl string, isHttps bool, suffix string) (ctx UrlContext, err error) {
var defaultPort int
if isHttps {
defaultPort = DefaultHttpsPort
} else {
defaultPort = DefaultHttpPort
}
ctx, err = ParseUrl(rawUrl, defaultPort)
if err != nil {
return
}
if (ctx.Scheme != "http" && ctx.Scheme != "https") || ctx.Host == "" || ctx.Path == "" || !strings.HasSuffix(ctx.LastItemOfPath, suffix) {
return ctx, ErrUrl
}
return
}