Files
ipchub/av/format/rtsp/request.go
2020-12-30 11:10:00 +08:00

284 lines
8.1 KiB
Go
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (c) 2019,CAOHONGJU All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package rtsp
import (
"bufio"
"bytes"
"crypto/md5"
"encoding/base64"
"encoding/hex"
"io"
"net/url"
"strings"
"github.com/cnotch/ipchub/utils/scan"
)
const (
rtspProto = "RTSP/1.0" // RTSP协议版本
basicAuthPrefix = "Basic " // 用户基础验证前缀
digestAuthPrefix = "Digest " // 摘要认证前缀
rtspURLPrefix = "rtsp://" // RTSP地址前缀
)
// 通用的 RTSP 方法。
//
// 除非特别说明,这些定义在 RFC2326 规范的 10 章中。
// 未实现的方法需要返回 "501 Not Implemented"
const (
MethodOptions = "OPTIONS" // 查询命令支持情况(C->S, S->C)
MethodDescribe = "DESCRIBE" // 获取媒体信息(C->S)
MethodAnnounce = "ANNOUNCE" // 声明要push的媒体信息(方向C->S, S->C)
MethodSetup = "SETUP" // 构建传输会话,也可以调整传输参数(C->S);如果不允许调整,可以返回 455 错误
MethodPlay = "PLAY" // 开始发送媒体数据(C->S)
MethodPause = "PAUSE" // 暂停发送媒体数据(C->S)
MethodTeardown = "TEARDOWN" // 关闭发送通道;关闭后需要重新执行 Setup 方法(C->S)
MethodGetParameter = "GET_PARAMETER" // 获取参数空body可作为心跳ping(C->S, S->C)
MethodSetParameter = "SET_PARAMETER" // 设置参数,应该每次只设置一个参数(C->S, S->C)
MethodRecord = "RECORD" // 启动录像(C->S)
MethodRedirect = "REDIRECT" // 跳转(S->C)
)
// Request 表示一个 RTSP 请求;
// 它可以是 sever 接收或发送的,也可以是 client 接收或发送的。
type Request struct {
Method string // RTSP 的方法OPTIONS、DESCRIBE...
URL *url.URL // 请求的 URI。
Proto string // 协议版本,默认 "RTSP/1.0"
Header Header // 包含请求的头字段。
Body string // 请求的消息体。
}
// ReadRequest 根据规范从 r 中读取 Request
func ReadRequest(r *bufio.Reader) (*Request, error) {
var err error
// 读取并解析首行
var line string
line, err = readLine(r)
if err != nil {
return nil, err
}
var req = new(Request)
s1 := strings.Index(line, " ")
s2 := strings.Index(line[s1+1:], " ")
if s1 < 0 || s2 < 0 {
return nil, &badStringError{"malformed RTSP request", line}
}
s2 += s1 + 1
// 解析结束
method := strings.TrimSpace(line[:s1])
rurl := strings.TrimSpace(line[s1+1 : s2])
proto := strings.TrimSpace(line[s2+1:])
// 检查请求的首行内容
// 检查方法命令名
if len(method) == 0 || method[0] == '$' { // RTP 包的第一个字节是 `$`
return nil, &badStringError{"invalid method", method}
}
// 只有 "OPTIONS" 命令的请求的URL为 *
if method != MethodOptions && rurl == "*" {
return nil, &badStringError{"invalid Request-URI", rurl}
}
if req.URL, err = url.ParseRequestURI(rurl); err != nil {
return nil, err
}
req.Method = method
req.Proto = proto
// 去除结尾的 :
if strings.LastIndex(req.URL.Host, ":") > strings.LastIndex(req.URL.Host, "]") {
req.URL.Host = strings.TrimSuffix(req.URL.Host, ":")
}
// 读取 Header
if req.Header, err = ReadHeader(r); err != nil {
return nil, err
}
// 读取Body
cl := req.Header.Int(FieldContentLength)
if cl > 0 {
// 读取 n 字节的字串Body
body := make([]byte, cl)
_, err = io.ReadFull(r, body)
req.Body = string(body)
}
return req, nil
}
// Write 根据规范将 Request 输出到 w
func (req *Request) Write(w io.Writer) error {
ws, ok := w.(writeStringer)
if !ok {
ws = stringWriter{w}
}
// TODO如果存在用户名密码去掉
ruri := req.URL.String()
// 写请求方法行
ws.WriteString(req.Method)
ws.WriteString(" ")
ws.WriteString(ruri)
ws.WriteString(" RTSP/1.0\r\n")
// 写 Header
if len(req.Body) > 0 {
req.Header.SetInt(FieldContentLength, len(req.Body))
} else {
delete(req.Header, FieldContentLength)
}
if err := req.Header.Write(w); err != nil {
return err
}
// 写 Content
if len(req.Body) > 0 {
if _, err := ws.WriteString(req.Body); err != nil {
return err
}
}
return nil
}
func (req *Request) String() string {
buf := bytes.Buffer{}
req.Write(&buf)
return buf.String()
}
// BasicAuth returns the username and password provided in the request's
// Authorization header, if the request uses HTTP Basic Authentication.
// See RFC 2617, Section 2.
func (req *Request) BasicAuth() (username, password string, ok bool) {
auth := req.Header.get(FieldAuthorization)
if auth == "" {
return
}
return parseBasicAuth(auth)
}
// SetBasicAuth sets the request's Authorization header to use HTTP
// Basic Authentication with the provided username and password.
//
// With HTTP Basic Authentication the provided username and password
// are not encrypted.
func (req *Request) SetBasicAuth(username, password string) {
req.Header.set(FieldAuthorization, formatBasicAuth(username, password))
}
func formatBasicAuth(username, password string) string {
auth := username + ":" + password
return basicAuthPrefix + base64.StdEncoding.EncodeToString([]byte(auth))
}
// parseBasicAuth parses an HTTP Basic Authentication string.
// "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true).
func parseBasicAuth(auth string) (username, password string, ok bool) {
// Case insensitive prefix match. See Issue 22736.
if len(auth) < len(basicAuthPrefix) || !strings.EqualFold(auth[:len(basicAuthPrefix)], basicAuthPrefix) {
return
}
c, err := base64.StdEncoding.DecodeString(auth[len(basicAuthPrefix):])
if err != nil {
return
}
cs := string(c)
s := strings.IndexByte(cs, ':')
if s < 0 {
return
}
return cs[:s], cs[s+1:], true
}
// DigestAuth 获取摘要认证信息
func (req *Request) DigestAuth() (username, response string, ok bool) {
auth := req.Header.get(FieldAuthorization)
// Case insensitive prefix match. See Issue 22736.
if len(auth) < len(digestAuthPrefix) || !strings.EqualFold(auth[:len(digestAuthPrefix)], digestAuthPrefix) {
return
}
auth = auth[len(digestAuthPrefix):]
tailing := auth
substr := ""
continueScan := true
for continueScan {
tailing, substr, continueScan = scan.Comma.Scan(tailing)
k, v, _ := scan.EqualPair.Scan(substr)
switch k {
case "username":
username = v
case "response":
response = v
}
}
return username, response, len(username) > 0 && len(response) > 0
}
// SetDigestAuth 为请求设置数字认证
func (req *Request) SetDigestAuth(url *url.URL, realm, nonce, username, password string) {
req.Header.set(FieldAuthorization, formatDigestAuth(realm, nonce, req.Method, url.String(), username, password))
}
// formatDigestAuth .
func formatDigestAuth(realm, nonce, method, url string, username, password string) string {
response := FormatDigestAuthResponse(realm, nonce, method, url, username, password)
buf := bytes.Buffer{}
buf.WriteString(`Digest username="`)
buf.WriteString(username)
buf.WriteString(`", realm="`)
buf.WriteString(realm)
buf.WriteString(`", nonce="`)
buf.WriteString(nonce)
buf.WriteString(`", uri="`)
buf.WriteString(url)
buf.WriteString(`", response="`)
buf.WriteString(response)
buf.WriteByte('"')
return buf.String()
}
// FormatDigestAuthResponse response= md5(md5(username:realm:password):nonce:md5(public_method:url));
func FormatDigestAuthResponse(realm, nonce, method, url string, username, password string) string {
buf := bytes.Buffer{}
// response= md5(md5(username:realm:password):nonce:md5(public_method:url));
buf.WriteString(username)
buf.WriteByte(':')
buf.WriteString(realm)
buf.WriteByte(':')
buf.WriteString(password)
md5Digest := md5.Sum(buf.Bytes())
md5UserRealmPwd := hex.EncodeToString(md5Digest[:])
buf.Reset()
buf.WriteString(method)
buf.WriteByte(':')
buf.WriteString(url)
md5Digest = md5.Sum(buf.Bytes())
md5MethodURL := hex.EncodeToString(md5Digest[:])
buf.Reset()
buf.WriteString(md5UserRealmPwd)
buf.WriteByte(':')
buf.WriteString(nonce)
buf.WriteByte(':')
buf.WriteString(md5MethodURL)
md5Digest = md5.Sum(buf.Bytes())
return hex.EncodeToString(md5Digest[:])
}