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 }