credentials.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. package digest
  2. import (
  3. "errors"
  4. "fmt"
  5. "strconv"
  6. "strings"
  7. "github.com/icholy/digest/internal/param"
  8. )
  9. // Credentials is a parsed version of the Authorization header
  10. type Credentials struct {
  11. Username string
  12. Realm string
  13. Nonce string
  14. URI string
  15. Response string
  16. Algorithm string
  17. Cnonce string
  18. Opaque string
  19. QOP string
  20. Nc int
  21. Userhash bool
  22. }
  23. // ParseCredentials parses the Authorization header value into credentials
  24. func ParseCredentials(s string) (*Credentials, error) {
  25. s, ok := strings.CutPrefix(s, Prefix)
  26. if !ok {
  27. return nil, errors.New("digest: invalid credentials prefix")
  28. }
  29. pp, err := param.Parse(s)
  30. if err != nil {
  31. return nil, fmt.Errorf("digest: invalid credentials: %w", err)
  32. }
  33. var c Credentials
  34. for _, p := range pp {
  35. switch p.Key {
  36. case "username":
  37. c.Username = p.Value
  38. case "realm":
  39. c.Realm = p.Value
  40. case "nonce":
  41. c.Nonce = p.Value
  42. case "uri":
  43. c.URI = p.Value
  44. case "response":
  45. c.Response = p.Value
  46. case "algorithm":
  47. c.Algorithm = p.Value
  48. case "cnonce":
  49. c.Cnonce = p.Value
  50. case "opaque":
  51. c.Opaque = p.Value
  52. case "qop":
  53. c.QOP = p.Value
  54. case "nc":
  55. nc, err := strconv.ParseInt(p.Value, 16, 32)
  56. if err != nil {
  57. return nil, fmt.Errorf("digest: invalid nc: %w", err)
  58. }
  59. c.Nc = int(nc)
  60. case "userhash":
  61. c.Userhash = strings.ToLower(p.Value) == "true"
  62. }
  63. }
  64. return &c, nil
  65. }
  66. // String formats the credentials into the header format
  67. func (c *Credentials) String() string {
  68. var pp []param.Param
  69. pp = append(pp,
  70. param.Param{
  71. Key: "username",
  72. Value: c.Username,
  73. Quote: true,
  74. },
  75. param.Param{
  76. Key: "realm",
  77. Value: c.Realm,
  78. Quote: true,
  79. },
  80. param.Param{
  81. Key: "nonce",
  82. Value: c.Nonce,
  83. Quote: true,
  84. },
  85. param.Param{
  86. Key: "uri",
  87. Value: c.URI,
  88. Quote: true,
  89. },
  90. )
  91. if c.Algorithm != "" {
  92. pp = append(pp, param.Param{
  93. Key: "algorithm",
  94. Value: c.Algorithm,
  95. })
  96. }
  97. if c.QOP != "" {
  98. pp = append(pp, param.Param{
  99. Key: "cnonce",
  100. Value: c.Cnonce,
  101. Quote: true,
  102. })
  103. }
  104. if c.Opaque != "" {
  105. pp = append(pp, param.Param{
  106. Key: "opaque",
  107. Value: c.Opaque,
  108. Quote: true,
  109. })
  110. }
  111. if c.QOP != "" {
  112. pp = append(pp,
  113. param.Param{
  114. Key: "qop",
  115. Value: c.QOP,
  116. },
  117. param.Param{
  118. Key: "nc",
  119. Value: fmt.Sprintf("%08x", c.Nc),
  120. },
  121. )
  122. }
  123. if c.Userhash {
  124. pp = append(pp, param.Param{
  125. Key: "userhash",
  126. Value: "true",
  127. })
  128. }
  129. // The RFC does not specify an order, but some implementations expect the response to be at the end.
  130. // See: https://github.com/icholy/digest/issues/8
  131. pp = append(pp, param.Param{
  132. Key: "response",
  133. Value: c.Response,
  134. Quote: true,
  135. })
  136. return Prefix + param.Format(pp...)
  137. }