Files
cloudpods/vendor/github.com/icholy/digest/challenge.go
2025-10-22 14:40:55 +08:00

156 lines
3.1 KiB
Go

package digest
import (
"errors"
"fmt"
"net/http"
"strings"
"github.com/icholy/digest/internal/param"
)
// Challenge is a challenge sent in the WWW-Authenticate header
type Challenge struct {
Realm string
Domain []string
Nonce string
Opaque string
Stale bool
Algorithm string
QOP []string
Charset string
Userhash bool
}
// SupportsQOP returns true if the challenge advertises support
// for the provided qop value
func (c *Challenge) SupportsQOP(qop string) bool {
for _, v := range c.QOP {
if v == qop {
return true
}
}
return false
}
// ParseChallenge parses the WWW-Authenticate header challenge
func ParseChallenge(s string) (*Challenge, error) {
s, ok := strings.CutPrefix(s, Prefix)
if !ok {
return nil, errors.New("digest: invalid challenge prefix")
}
pp, err := param.Parse(s)
if err != nil {
return nil, fmt.Errorf("digest: invalid challenge: %w", err)
}
var c Challenge
for _, p := range pp {
switch p.Key {
case "realm":
c.Realm = p.Value
case "domain":
c.Domain = strings.Fields(p.Value)
case "nonce":
c.Nonce = p.Value
case "algorithm":
c.Algorithm = p.Value
case "stale":
c.Stale = strings.ToLower(p.Value) == "true"
case "opaque":
c.Opaque = p.Value
case "qop":
c.QOP = strings.Split(p.Value, ",")
case "charset":
c.Charset = p.Value
case "userhash":
c.Userhash = strings.ToLower(p.Value) == "true"
}
}
return &c, nil
}
// String returns the foramtted header value
func (c *Challenge) String() string {
var pp []param.Param
pp = append(pp, param.Param{
Key: "realm",
Value: c.Realm,
Quote: true,
})
if len(c.Domain) != 0 {
pp = append(pp, param.Param{
Key: "domain",
Value: strings.Join(c.Domain, " "),
Quote: true,
})
}
pp = append(pp, param.Param{
Key: "nonce",
Value: c.Nonce,
Quote: true,
})
if c.Opaque != "" {
pp = append(pp, param.Param{
Key: "opaque",
Value: c.Opaque,
Quote: true,
})
}
if c.Stale {
pp = append(pp, param.Param{
Key: "stale",
Value: "true",
})
}
if c.Algorithm != "" {
pp = append(pp, param.Param{
Key: "algorithm",
Value: c.Algorithm,
})
}
if len(c.QOP) != 0 {
pp = append(pp, param.Param{
Key: "qop",
Value: strings.Join(c.QOP, ","),
Quote: true,
})
}
if c.Charset != "" {
pp = append(pp, param.Param{
Key: "charset",
Value: c.Charset,
})
}
if c.Userhash {
pp = append(pp, param.Param{
Key: "userhash",
Value: "true",
})
}
return Prefix + param.Format(pp...)
}
// ErrNoChallenge indicates that no WWW-Authenticate headers were found.
var ErrNoChallenge = errors.New("digest: no challenge found")
// FindChallenge returns the first supported challenge in the headers
func FindChallenge(h http.Header) (*Challenge, error) {
var last error
for _, header := range h.Values("WWW-Authenticate") {
if !IsDigest(header) {
continue
}
chal, err := ParseChallenge(header)
if err == nil && CanDigest(chal) {
return chal, nil
}
if err != nil {
last = err
}
}
if last != nil {
return nil, last
}
return nil, ErrNoChallenge
}