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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 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
}