| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- 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
- }
|