mirror of https://github.com/EasyDarwin/EasyDarwin
1 support digest authenticate
parent
e02b12bb68
commit
51f27c769d
|
@ -3,12 +3,14 @@ package rtsp
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -29,7 +31,6 @@ type RTSPClient struct {
|
||||||
CustomPath string //custom path for pusher
|
CustomPath string //custom path for pusher
|
||||||
ID string
|
ID string
|
||||||
Conn net.Conn
|
Conn net.Conn
|
||||||
AuthHeaders bool
|
|
||||||
Session *string
|
Session *string
|
||||||
Seq int
|
Seq int
|
||||||
connRW *bufio.ReadWriter
|
connRW *bufio.ReadWriter
|
||||||
|
@ -45,6 +46,8 @@ type RTSPClient struct {
|
||||||
OptionIntervalMillis int64
|
OptionIntervalMillis int64
|
||||||
SDPRaw string
|
SDPRaw string
|
||||||
|
|
||||||
|
authLine string
|
||||||
|
|
||||||
//tcp channels
|
//tcp channels
|
||||||
aRTPChannel int
|
aRTPChannel int
|
||||||
aRTPControlChannel int
|
aRTPControlChannel int
|
||||||
|
@ -80,6 +83,78 @@ func NewRTSPClient(server *Server, rawUrl string, sendOptionMillis int64) (clien
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func digestAuth(authLine string, method string, URL string) (string, error) {
|
||||||
|
l, err := url.Parse(URL)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Url parse error:%v,%v", URL, err)
|
||||||
|
}
|
||||||
|
realm := ""
|
||||||
|
nonce := ""
|
||||||
|
realmRex := regexp.MustCompile(`realm="(\S+)"`)
|
||||||
|
result1 := realmRex.FindStringSubmatch(authLine)
|
||||||
|
|
||||||
|
nonceRex := regexp.MustCompile(`nonce="(\S+)"`)
|
||||||
|
result2 := nonceRex.FindStringSubmatch(authLine)
|
||||||
|
|
||||||
|
if len(result1) == 2 {
|
||||||
|
realm = result1[1]
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("auth error : no realm found")
|
||||||
|
}
|
||||||
|
if len(result2) == 2 {
|
||||||
|
nonce = result2[1]
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("auth error : no nonce found")
|
||||||
|
}
|
||||||
|
// response= md5(md5(username:realm:password):nonce:md5(public_method:url));
|
||||||
|
username := l.User.Username()
|
||||||
|
password, _ := l.User.Password()
|
||||||
|
l.User = nil
|
||||||
|
md5UserRealmPwd := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%s:%s:%s", username, realm, password))))
|
||||||
|
md5MethodURL := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%s:%s", method, l.String()))))
|
||||||
|
|
||||||
|
response := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%s:%s:%s", md5UserRealmPwd, nonce, md5MethodURL))))
|
||||||
|
Authorization := fmt.Sprintf("Authorization: Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"", username, realm, nonce, l.String(), response)
|
||||||
|
return Authorization, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client *RTSPClient) checkAuth(method string, resp *Response) (string, error) {
|
||||||
|
if resp.StatusCode == 401 {
|
||||||
|
|
||||||
|
// need auth.
|
||||||
|
AuthHeaders := resp.Header["WWW-Authenticate"]
|
||||||
|
auths, ok := AuthHeaders.([]string)
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
for _, authLine := range auths {
|
||||||
|
|
||||||
|
if strings.IndexAny(authLine, "Digest") == 0 {
|
||||||
|
// realm="HipcamRealServer",
|
||||||
|
// nonce="3b27a446bfa49b0c48c3edb83139543d"
|
||||||
|
client.authLine = authLine
|
||||||
|
return digestAuth(authLine, method, client.URL)
|
||||||
|
} else if strings.IndexAny(authLine, "Basic") == 0 {
|
||||||
|
// not support yet
|
||||||
|
// TODO..
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("auth error")
|
||||||
|
} else {
|
||||||
|
authLine, _ := AuthHeaders.(string)
|
||||||
|
if strings.IndexAny(authLine, "Digest") == 0 {
|
||||||
|
client.authLine = authLine
|
||||||
|
return digestAuth(authLine, method, client.URL)
|
||||||
|
} else if strings.IndexAny(authLine, "Basic") == 0 {
|
||||||
|
// not support yet
|
||||||
|
// TODO..
|
||||||
|
return "", fmt.Errorf("not support Basic auth yet")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
func (client *RTSPClient) Start(timeout time.Duration) error {
|
func (client *RTSPClient) Start(timeout time.Duration) error {
|
||||||
//source := make(chan interface{})
|
//source := make(chan interface{})
|
||||||
|
|
||||||
|
@ -132,7 +207,17 @@ func (client *RTSPClient) Start(timeout time.Duration) error {
|
||||||
// An OPTIONS request returns the request types the server will accept.
|
// An OPTIONS request returns the request types the server will accept.
|
||||||
resp, err := client.Request("OPTIONS", headers)
|
resp, err := client.Request("OPTIONS", headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
Authorization, _ := client.checkAuth("OPTIONS", resp)
|
||||||
|
if len(Authorization) > 0 {
|
||||||
|
headers := make(map[string]string)
|
||||||
|
headers["Require"] = "implicit-play"
|
||||||
|
headers["Authorization"] = Authorization
|
||||||
|
// An OPTIONS request returns the request types the server will accept.
|
||||||
|
resp, err = client.Request("OPTIONS", headers)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A DESCRIBE request includes an RTSP URL (rtsp://...), and the type of reply data that can be handled. This reply includes the presentation description,
|
// A DESCRIBE request includes an RTSP URL (rtsp://...), and the type of reply data that can be handled. This reply includes the presentation description,
|
||||||
|
@ -142,6 +227,15 @@ func (client *RTSPClient) Start(timeout time.Duration) error {
|
||||||
headers["Accept"] = "application/sdp"
|
headers["Accept"] = "application/sdp"
|
||||||
resp, err = client.Request("DESCRIBE", headers)
|
resp, err = client.Request("DESCRIBE", headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Authorization, _ := client.checkAuth("DESCRIBE", resp)
|
||||||
|
if len(Authorization) > 0 {
|
||||||
|
headers := make(map[string]string)
|
||||||
|
headers["Authorization"] = Authorization
|
||||||
|
resp, err = client.Request("DESCRIBE", headers)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,11 +259,12 @@ func (client *RTSPClient) Start(timeout time.Duration) error {
|
||||||
}
|
}
|
||||||
headers = make(map[string]string)
|
headers = make(map[string]string)
|
||||||
headers["Transport"] = fmt.Sprintf("RTP/AVP/TCP;unicast;interleaved=%d-%d", client.vRTPChannel, client.vRTPControlChannel)
|
headers["Transport"] = fmt.Sprintf("RTP/AVP/TCP;unicast;interleaved=%d-%d", client.vRTPChannel, client.vRTPControlChannel)
|
||||||
|
|
||||||
resp, err = client.RequestWithPath("SETUP", _url, headers, true)
|
resp, err = client.RequestWithPath("SETUP", _url, headers, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
Session = resp.Header["Session"]
|
Session, _ = resp.Header["Session"].(string)
|
||||||
case "audio":
|
case "audio":
|
||||||
client.AControl = media.Attributes.Get("control")
|
client.AControl = media.Attributes.Get("control")
|
||||||
client.VCodec = media.Formats[0].Name
|
client.VCodec = media.Formats[0].Name
|
||||||
|
@ -185,7 +280,7 @@ func (client *RTSPClient) Start(timeout time.Duration) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
Session = resp.Header["Session"]
|
Session, _ = resp.Header["Session"].(string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
headers = make(map[string]string)
|
headers = make(map[string]string)
|
||||||
|
@ -376,8 +471,13 @@ func (client *RTSPClient) Stop() {
|
||||||
|
|
||||||
func (client *RTSPClient) RequestWithPath(method string, path string, headers map[string]string, needResp bool) (resp *Response, err error) {
|
func (client *RTSPClient) RequestWithPath(method string, path string, headers map[string]string, needResp bool) (resp *Response, err error) {
|
||||||
headers["User-Agent"] = "EasyDarwinGo"
|
headers["User-Agent"] = "EasyDarwinGo"
|
||||||
if client.AuthHeaders {
|
if len(headers["Authorization"]) == 0 {
|
||||||
//headers["Authorization"] = this.digest(method, _url);
|
if len(client.authLine) != 0 {
|
||||||
|
Authorization, _ := digestAuth(client.authLine, method, client.URL)
|
||||||
|
if len(Authorization) > 0 {
|
||||||
|
headers["Authorization"] = Authorization
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if client.Session != nil {
|
if client.Session != nil {
|
||||||
headers["Session"] = *client.Session
|
headers["Session"] = *client.Session
|
||||||
|
@ -408,7 +508,7 @@ func (client *RTSPClient) RequestWithPath(method string, path string, headers ma
|
||||||
status := ""
|
status := ""
|
||||||
sid := ""
|
sid := ""
|
||||||
contentLen := 0
|
contentLen := 0
|
||||||
respHeader := make(map[string]string)
|
respHeader := make(map[string]interface{})
|
||||||
var line []byte
|
var line []byte
|
||||||
builder.Reset()
|
builder.Reset()
|
||||||
for !client.Stoped {
|
for !client.Stoped {
|
||||||
|
@ -429,9 +529,15 @@ func (client *RTSPClient) RequestWithPath(method string, path string, headers ma
|
||||||
builder.Write(content)
|
builder.Write(content)
|
||||||
}
|
}
|
||||||
resp = NewResponse(statusCode, status, strconv.Itoa(cseq), sid, body)
|
resp = NewResponse(statusCode, status, strconv.Itoa(cseq), sid, body)
|
||||||
|
resp.Header = respHeader
|
||||||
|
|
||||||
log.Println("S->C <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
|
log.Println("S->C <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
|
||||||
log.Println(builder.String())
|
log.Println(builder.String())
|
||||||
|
|
||||||
|
if !(statusCode >= 200 && statusCode <= 300) {
|
||||||
|
err = fmt.Errorf("Response StatusCode is :%d", statusCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s := string(line)
|
s := string(line)
|
||||||
|
@ -450,16 +556,23 @@ func (client *RTSPClient) RequestWithPath(method string, path string, headers ma
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if statusCode != 200 {
|
|
||||||
err = fmt.Errorf("Response StatusCode is :%d", statusCode)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
status = splits[2]
|
status = splits[2]
|
||||||
}
|
}
|
||||||
lineCount++
|
lineCount++
|
||||||
splits := strings.Split(s, ":")
|
splits := strings.Split(s, ":")
|
||||||
if len(splits) == 2 {
|
if len(splits) == 2 {
|
||||||
respHeader[splits[0]] = strings.TrimSpace(splits[1])
|
if val, ok := respHeader[splits[0]]; ok {
|
||||||
|
if slice, ok2 := val.([]string); ok2 {
|
||||||
|
slice = append(slice, strings.TrimSpace(splits[1]))
|
||||||
|
respHeader[splits[0]] = slice
|
||||||
|
} else {
|
||||||
|
str, _ := val.(string)
|
||||||
|
slice := []string{str, strings.TrimSpace(splits[1])}
|
||||||
|
respHeader[splits[0]] = slice
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
respHeader[splits[0]] = strings.TrimSpace(splits[1])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if strings.Index(s, "Session:") == 0 {
|
if strings.Index(s, "Session:") == 0 {
|
||||||
splits := strings.Split(s, ":")
|
splits := strings.Split(s, ":")
|
||||||
|
@ -488,12 +601,22 @@ func (client *RTSPClient) RequestWithPath(method string, path string, headers ma
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *RTSPClient) Request(method string, headers map[string]string) (resp *Response, err error) {
|
func (client *RTSPClient) Request(method string, headers map[string]string) (*Response, error) {
|
||||||
return client.RequestWithPath(method, client.URL, headers, true)
|
l, err := url.Parse(client.URL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Url parse error:%v", err)
|
||||||
|
}
|
||||||
|
l.User = nil
|
||||||
|
return client.RequestWithPath(method, l.String(), headers, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *RTSPClient) RequestNoResp(method string, headers map[string]string) (err error) {
|
func (client *RTSPClient) RequestNoResp(method string, headers map[string]string) (err error) {
|
||||||
if _, err := client.RequestWithPath(method, client.URL, headers, false); err != nil {
|
l, err := url.Parse(client.URL)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Url parse error:%v", err)
|
||||||
|
}
|
||||||
|
l.User = nil
|
||||||
|
if _, err = client.RequestWithPath(method, l.String(), headers, false); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -9,7 +9,7 @@ type Response struct {
|
||||||
Version string
|
Version string
|
||||||
StatusCode int
|
StatusCode int
|
||||||
Status string
|
Status string
|
||||||
Header map[string]string
|
Header map[string]interface{}
|
||||||
Body string
|
Body string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ func NewResponse(statusCode int, status, cSeq, sid, body string) *Response {
|
||||||
Version: RTSP_VERSION,
|
Version: RTSP_VERSION,
|
||||||
StatusCode: statusCode,
|
StatusCode: statusCode,
|
||||||
Status: status,
|
Status: status,
|
||||||
Header: map[string]string{"CSeq": cSeq, "Session": sid},
|
Header: map[string]interface{}{"CSeq": cSeq, "Session": sid},
|
||||||
Body: body,
|
Body: body,
|
||||||
}
|
}
|
||||||
len := len(body)
|
len := len(body)
|
||||||
|
|
Loading…
Reference in New Issue