github.com/cnotch/ipchub@v1.1.0/av/format/rtsp/request.go (about)

     1  // Copyright (c) 2019,CAOHONGJU All rights reserved.
     2  // Use of this source code is governed by a MIT-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package rtsp
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"crypto/md5"
    11  	"encoding/base64"
    12  	"encoding/hex"
    13  	"io"
    14  	"net/url"
    15  	"strings"
    16  
    17  	"github.com/cnotch/ipchub/utils/scan"
    18  )
    19  
    20  const (
    21  	rtspProto        = "RTSP/1.0" // RTSP协议版本
    22  	basicAuthPrefix  = "Basic "   // 用户基础验证前缀
    23  	digestAuthPrefix = "Digest "  // 摘要认证前缀
    24  	rtspURLPrefix    = "rtsp://"  // RTSP地址前缀
    25  )
    26  
    27  // 通用的 RTSP 方法。
    28  //
    29  // 除非特别说明,这些定义在 RFC2326 规范的 10 章中。
    30  // 未实现的方法需要返回 "501 Not Implemented"
    31  const (
    32  	MethodOptions      = "OPTIONS"       // 查询命令支持情况(C->S, S->C)
    33  	MethodDescribe     = "DESCRIBE"      // 获取媒体信息(C->S)
    34  	MethodAnnounce     = "ANNOUNCE"      // 声明要push的媒体信息(方向:C->S, S->C)
    35  	MethodSetup        = "SETUP"         // 构建传输会话,也可以调整传输参数(C->S);如果不允许调整,可以返回 455 错误
    36  	MethodPlay         = "PLAY"          // 开始发送媒体数据(C->S)
    37  	MethodPause        = "PAUSE"         // 暂停发送媒体数据(C->S)
    38  	MethodTeardown     = "TEARDOWN"      // 关闭发送通道;关闭后需要重新执行 Setup 方法(C->S)
    39  	MethodGetParameter = "GET_PARAMETER" // 获取参数;空body可作为心跳ping(C->S, S->C)
    40  	MethodSetParameter = "SET_PARAMETER" // 设置参数,应该每次只设置一个参数(C->S, S->C)
    41  	MethodRecord       = "RECORD"        // 启动录像(C->S)
    42  	MethodRedirect     = "REDIRECT"      // 跳转(S->C)
    43  )
    44  
    45  // Request 表示一个 RTSP 请求;
    46  // 它可以是 sever 接收或发送的,也可以是 client 接收或发送的。
    47  type Request struct {
    48  	Method string   // RTSP 的方法(OPTIONS、DESCRIBE...)
    49  	URL    *url.URL // 请求的 URI。
    50  	Proto  string   // 协议版本,默认 "RTSP/1.0"
    51  	Header Header   // 包含请求的头字段。
    52  	Body   string   // 请求的消息体。
    53  }
    54  
    55  // ReadRequest 根据规范从 r 中读取 Request
    56  func ReadRequest(r *bufio.Reader) (*Request, error) {
    57  	var err error
    58  
    59  	// 读取并解析首行
    60  	var line string
    61  	line, err = readLine(r)
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  
    66  	var req = new(Request)
    67  	s1 := strings.Index(line, " ")
    68  	s2 := strings.Index(line[s1+1:], " ")
    69  	if s1 < 0 || s2 < 0 {
    70  		return nil, &badStringError{"malformed RTSP request", line}
    71  	}
    72  	s2 += s1 + 1
    73  
    74  	// 解析结束
    75  	method := strings.TrimSpace(line[:s1])
    76  	rurl := strings.TrimSpace(line[s1+1 : s2])
    77  	proto := strings.TrimSpace(line[s2+1:])
    78  
    79  	// 检查请求的首行内容
    80  	// 检查方法命令名
    81  	if len(method) == 0 || method[0] == '$' { // RTP 包的第一个字节是 `$`
    82  		return nil, &badStringError{"invalid method", method}
    83  	}
    84  	// 只有 "OPTIONS" 命令的请求的URL为 *
    85  	if method != MethodOptions && rurl == "*" {
    86  		return nil, &badStringError{"invalid Request-URI", rurl}
    87  	}
    88  	if req.URL, err = url.ParseRequestURI(rurl); err != nil {
    89  		return nil, err
    90  	}
    91  	req.Method = method
    92  	req.Proto = proto
    93  	// 去除结尾的 :
    94  	if strings.LastIndex(req.URL.Host, ":") > strings.LastIndex(req.URL.Host, "]") {
    95  		req.URL.Host = strings.TrimSuffix(req.URL.Host, ":")
    96  	}
    97  
    98  	// 读取 Header
    99  	if req.Header, err = ReadHeader(r); err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	// 读取Body
   104  	cl := req.Header.Int(FieldContentLength)
   105  	if cl > 0 {
   106  		// 读取 n 字节的字串Body
   107  		body := make([]byte, cl)
   108  		_, err = io.ReadFull(r, body)
   109  		req.Body = string(body)
   110  	}
   111  	return req, nil
   112  }
   113  
   114  // Write 根据规范将 Request 输出到 w
   115  func (req *Request) Write(w io.Writer) error {
   116  	ws, ok := w.(writeStringer)
   117  	if !ok {
   118  		ws = stringWriter{w}
   119  	}
   120  
   121  	// TODO:如果存在用户名密码,去掉;
   122  	ruri := req.URL.String()
   123  
   124  	// 写请求方法行
   125  	ws.WriteString(req.Method)
   126  	ws.WriteString(" ")
   127  	ws.WriteString(ruri)
   128  	ws.WriteString(" RTSP/1.0\r\n")
   129  
   130  	// 写 Header
   131  	if len(req.Body) > 0 {
   132  		req.Header.SetInt(FieldContentLength, len(req.Body))
   133  	} else {
   134  		delete(req.Header, FieldContentLength)
   135  	}
   136  	if err := req.Header.Write(w); err != nil {
   137  		return err
   138  	}
   139  
   140  	// 写 Content
   141  	if len(req.Body) > 0 {
   142  		if _, err := ws.WriteString(req.Body); err != nil {
   143  			return err
   144  		}
   145  	}
   146  
   147  	return nil
   148  }
   149  
   150  func (req *Request) String() string {
   151  	buf := bytes.Buffer{}
   152  	req.Write(&buf)
   153  	return buf.String()
   154  }
   155  
   156  // BasicAuth returns the username and password provided in the request's
   157  // Authorization header, if the request uses HTTP Basic Authentication.
   158  // See RFC 2617, Section 2.
   159  func (req *Request) BasicAuth() (username, password string, ok bool) {
   160  	auth := req.Header.get(FieldAuthorization)
   161  	if auth == "" {
   162  		return
   163  	}
   164  	return parseBasicAuth(auth)
   165  }
   166  
   167  // SetBasicAuth sets the request's Authorization header to use HTTP
   168  // Basic Authentication with the provided username and password.
   169  //
   170  // With HTTP Basic Authentication the provided username and password
   171  // are not encrypted.
   172  func (req *Request) SetBasicAuth(username, password string) {
   173  	req.Header.set(FieldAuthorization, formatBasicAuth(username, password))
   174  }
   175  
   176  func formatBasicAuth(username, password string) string {
   177  	auth := username + ":" + password
   178  	return basicAuthPrefix + base64.StdEncoding.EncodeToString([]byte(auth))
   179  }
   180  
   181  // parseBasicAuth parses an HTTP Basic Authentication string.
   182  // "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true).
   183  func parseBasicAuth(auth string) (username, password string, ok bool) {
   184  	// Case insensitive prefix match. See Issue 22736.
   185  	if len(auth) < len(basicAuthPrefix) || !strings.EqualFold(auth[:len(basicAuthPrefix)], basicAuthPrefix) {
   186  		return
   187  	}
   188  	c, err := base64.StdEncoding.DecodeString(auth[len(basicAuthPrefix):])
   189  	if err != nil {
   190  		return
   191  	}
   192  	cs := string(c)
   193  	s := strings.IndexByte(cs, ':')
   194  	if s < 0 {
   195  		return
   196  	}
   197  	return cs[:s], cs[s+1:], true
   198  }
   199  
   200  // DigestAuth 获取摘要认证信息
   201  func (req *Request) DigestAuth() (username, response string, ok bool) {
   202  	auth := req.Header.get(FieldAuthorization)
   203  
   204  	// Case insensitive prefix match. See Issue 22736.
   205  	if len(auth) < len(digestAuthPrefix) || !strings.EqualFold(auth[:len(digestAuthPrefix)], digestAuthPrefix) {
   206  		return
   207  	}
   208  
   209  	auth = auth[len(digestAuthPrefix):]
   210  
   211  	advance := auth
   212  	token := ""
   213  	continueScan := true
   214  	for continueScan {
   215  		advance, token, continueScan = scan.Comma.Scan(advance)
   216  		k, v, _ := scan.EqualPair.Scan(token)
   217  		switch k {
   218  		case "username":
   219  			username = v
   220  		case "response":
   221  			response = v
   222  		}
   223  	}
   224  
   225  	return username, response, len(username) > 0 && len(response) > 0
   226  }
   227  
   228  // SetDigestAuth 为请求设置数字认证
   229  func (req *Request) SetDigestAuth(url *url.URL, realm, nonce, username, password string) {
   230  	req.Header.set(FieldAuthorization, formatDigestAuth(realm, nonce, req.Method, url.String(), username, password))
   231  }
   232  
   233  // formatDigestAuth .
   234  func formatDigestAuth(realm, nonce, method, url string, username, password string) string {
   235  	response := FormatDigestAuthResponse(realm, nonce, method, url, username, password)
   236  
   237  	buf := bytes.Buffer{}
   238  
   239  	buf.WriteString(`Digest username="`)
   240  	buf.WriteString(username)
   241  	buf.WriteString(`", realm="`)
   242  	buf.WriteString(realm)
   243  	buf.WriteString(`", nonce="`)
   244  	buf.WriteString(nonce)
   245  	buf.WriteString(`", uri="`)
   246  	buf.WriteString(url)
   247  	buf.WriteString(`", response="`)
   248  	buf.WriteString(response)
   249  	buf.WriteByte('"')
   250  	return buf.String()
   251  }
   252  
   253  // FormatDigestAuthResponse response= md5(md5(username:realm:password):nonce:md5(public_method:url));
   254  func FormatDigestAuthResponse(realm, nonce, method, url string, username, password string) string {
   255  	buf := bytes.Buffer{}
   256  
   257  	// response= md5(md5(username:realm:password):nonce:md5(public_method:url));
   258  	buf.WriteString(username)
   259  	buf.WriteByte(':')
   260  	buf.WriteString(realm)
   261  	buf.WriteByte(':')
   262  	buf.WriteString(password)
   263  
   264  	md5Digest := md5.Sum(buf.Bytes())
   265  	md5UserRealmPwd := hex.EncodeToString(md5Digest[:])
   266  
   267  	buf.Reset()
   268  	buf.WriteString(method)
   269  	buf.WriteByte(':')
   270  	buf.WriteString(url)
   271  	md5Digest = md5.Sum(buf.Bytes())
   272  	md5MethodURL := hex.EncodeToString(md5Digest[:])
   273  
   274  	buf.Reset()
   275  	buf.WriteString(md5UserRealmPwd)
   276  	buf.WriteByte(':')
   277  	buf.WriteString(nonce)
   278  	buf.WriteByte(':')
   279  	buf.WriteString(md5MethodURL)
   280  
   281  	md5Digest = md5.Sum(buf.Bytes())
   282  	return hex.EncodeToString(md5Digest[:])
   283  }