challenge.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. package digest
  2. import (
  3. "errors"
  4. "fmt"
  5. "net/http"
  6. "strings"
  7. "github.com/icholy/digest/internal/param"
  8. )
  9. // Challenge is a challenge sent in the WWW-Authenticate header
  10. type Challenge struct {
  11. Realm string
  12. Domain []string
  13. Nonce string
  14. Opaque string
  15. Stale bool
  16. Algorithm string
  17. QOP []string
  18. Charset string
  19. Userhash bool
  20. }
  21. // SupportsQOP returns true if the challenge advertises support
  22. // for the provided qop value
  23. func (c *Challenge) SupportsQOP(qop string) bool {
  24. for _, v := range c.QOP {
  25. if v == qop {
  26. return true
  27. }
  28. }
  29. return false
  30. }
  31. // ParseChallenge parses the WWW-Authenticate header challenge
  32. func ParseChallenge(s string) (*Challenge, error) {
  33. s, ok := strings.CutPrefix(s, Prefix)
  34. if !ok {
  35. return nil, errors.New("digest: invalid challenge prefix")
  36. }
  37. pp, err := param.Parse(s)
  38. if err != nil {
  39. return nil, fmt.Errorf("digest: invalid challenge: %w", err)
  40. }
  41. var c Challenge
  42. for _, p := range pp {
  43. switch p.Key {
  44. case "realm":
  45. c.Realm = p.Value
  46. case "domain":
  47. c.Domain = strings.Fields(p.Value)
  48. case "nonce":
  49. c.Nonce = p.Value
  50. case "algorithm":
  51. c.Algorithm = p.Value
  52. case "stale":
  53. c.Stale = strings.ToLower(p.Value) == "true"
  54. case "opaque":
  55. c.Opaque = p.Value
  56. case "qop":
  57. c.QOP = strings.Split(p.Value, ",")
  58. case "charset":
  59. c.Charset = p.Value
  60. case "userhash":
  61. c.Userhash = strings.ToLower(p.Value) == "true"
  62. }
  63. }
  64. return &c, nil
  65. }
  66. // String returns the foramtted header value
  67. func (c *Challenge) String() string {
  68. var pp []param.Param
  69. pp = append(pp, param.Param{
  70. Key: "realm",
  71. Value: c.Realm,
  72. Quote: true,
  73. })
  74. if len(c.Domain) != 0 {
  75. pp = append(pp, param.Param{
  76. Key: "domain",
  77. Value: strings.Join(c.Domain, " "),
  78. Quote: true,
  79. })
  80. }
  81. pp = append(pp, param.Param{
  82. Key: "nonce",
  83. Value: c.Nonce,
  84. Quote: true,
  85. })
  86. if c.Opaque != "" {
  87. pp = append(pp, param.Param{
  88. Key: "opaque",
  89. Value: c.Opaque,
  90. Quote: true,
  91. })
  92. }
  93. if c.Stale {
  94. pp = append(pp, param.Param{
  95. Key: "stale",
  96. Value: "true",
  97. })
  98. }
  99. if c.Algorithm != "" {
  100. pp = append(pp, param.Param{
  101. Key: "algorithm",
  102. Value: c.Algorithm,
  103. })
  104. }
  105. if len(c.QOP) != 0 {
  106. pp = append(pp, param.Param{
  107. Key: "qop",
  108. Value: strings.Join(c.QOP, ","),
  109. Quote: true,
  110. })
  111. }
  112. if c.Charset != "" {
  113. pp = append(pp, param.Param{
  114. Key: "charset",
  115. Value: c.Charset,
  116. })
  117. }
  118. if c.Userhash {
  119. pp = append(pp, param.Param{
  120. Key: "userhash",
  121. Value: "true",
  122. })
  123. }
  124. return Prefix + param.Format(pp...)
  125. }
  126. // ErrNoChallenge indicates that no WWW-Authenticate headers were found.
  127. var ErrNoChallenge = errors.New("digest: no challenge found")
  128. // FindChallenge returns the first supported challenge in the headers
  129. func FindChallenge(h http.Header) (*Challenge, error) {
  130. var last error
  131. for _, header := range h.Values("WWW-Authenticate") {
  132. if !IsDigest(header) {
  133. continue
  134. }
  135. chal, err := ParseChallenge(header)
  136. if err == nil && CanDigest(chal) {
  137. return chal, nil
  138. }
  139. if err != nil {
  140. last = err
  141. }
  142. }
  143. if last != nil {
  144. return nil, last
  145. }
  146. return nil, ErrNoChallenge
  147. }