auth.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. package tcp
  2. import (
  3. "crypto/md5"
  4. "encoding/base64"
  5. "encoding/hex"
  6. "fmt"
  7. "net/url"
  8. "strings"
  9. )
  10. type Auth struct {
  11. Method byte
  12. user string
  13. pass string
  14. header string
  15. h1nonce string
  16. }
  17. const (
  18. AuthNone byte = iota
  19. AuthUnknown
  20. AuthBasic
  21. AuthDigest
  22. AuthTPLink // https://drmnsamoliu.github.io/video.html
  23. )
  24. func NewAuth(user *url.Userinfo) *Auth {
  25. a := new(Auth)
  26. a.user = user.Username()
  27. a.pass, _ = user.Password()
  28. if a.user != "" {
  29. a.Method = AuthUnknown
  30. }
  31. return a
  32. }
  33. func (a *Auth) Read(res *Response) bool {
  34. auth := res.Header.Get("WWW-Authenticate")
  35. if len(auth) < 6 {
  36. return false
  37. }
  38. switch auth[:6] {
  39. case "Basic ":
  40. a.header = "Basic " + B64(a.user, a.pass)
  41. a.Method = AuthBasic
  42. return true
  43. case "Digest":
  44. realm := Between(auth, `realm="`, `"`)
  45. nonce := Between(auth, `nonce="`, `"`)
  46. a.h1nonce = HexMD5(a.user, realm, a.pass) + ":" + nonce
  47. a.header = fmt.Sprintf(
  48. `Digest username="%s", realm="%s", nonce="%s"`,
  49. a.user, realm, nonce,
  50. )
  51. a.Method = AuthDigest
  52. return true
  53. default:
  54. return false
  55. }
  56. }
  57. func (a *Auth) Write(req *Request) {
  58. if a == nil {
  59. return
  60. }
  61. switch a.Method {
  62. case AuthBasic:
  63. req.Header.Set("Authorization", a.header)
  64. case AuthDigest:
  65. // important to use String except RequestURL for RtspServer:
  66. // https://github.com/AlexxIT/go2rtc/issues/244
  67. uri := req.URL.String()
  68. h2 := HexMD5(req.Method, uri)
  69. response := HexMD5(a.h1nonce, h2)
  70. header := a.header + fmt.Sprintf(
  71. `, uri="%s", response="%s"`, uri, response,
  72. )
  73. req.Header.Set("Authorization", header)
  74. case AuthTPLink:
  75. req.URL.Host = "127.0.0.1"
  76. }
  77. }
  78. func (a *Auth) Validate(req *Request) bool {
  79. if a == nil {
  80. return true
  81. }
  82. header := req.Header.Get("Authorization")
  83. if header == "" {
  84. return false
  85. }
  86. if a.Method == AuthUnknown {
  87. a.Method = AuthBasic
  88. a.header = "Basic " + B64(a.user, a.pass)
  89. }
  90. return header == a.header
  91. }
  92. func (a *Auth) ReadNone(res *Response) bool {
  93. auth := res.Header.Get("WWW-Authenticate")
  94. if strings.Contains(auth, "TP-LINK Streaming Media") {
  95. a.Method = AuthTPLink
  96. return true
  97. }
  98. return false
  99. }
  100. func Between(s, sub1, sub2 string) string {
  101. i := strings.Index(s, sub1)
  102. if i < 0 {
  103. return ""
  104. }
  105. s = s[i+len(sub1):]
  106. i = strings.Index(s, sub2)
  107. if i < 0 {
  108. return ""
  109. }
  110. return s[:i]
  111. }
  112. func HexMD5(s ...string) string {
  113. b := md5.Sum([]byte(strings.Join(s, ":")))
  114. return hex.EncodeToString(b[:])
  115. }
  116. func B64(s ...string) string {
  117. b := []byte(strings.Join(s, ":"))
  118. return base64.StdEncoding.EncodeToString(b)
  119. }