| 74 | } |
| 75 | |
| 76 | func (da *digestAuth) HttpRoundTripWrapperFunc(rt http.RoundTripper) HttpRoundTripFunc { |
| 77 | return func(req *http.Request) (resp *http.Response, err error) { |
| 78 | clone, err := cloner(req) |
| 79 | if err != nil { |
| 80 | return nil, err |
| 81 | } |
| 82 | |
| 83 | // make a copy of the request |
| 84 | first, err := clone() |
| 85 | if err != nil { |
| 86 | return nil, err |
| 87 | } |
| 88 | |
| 89 | // prepare the first request using a cached challenge |
| 90 | if err := da.prepare(first); err != nil { |
| 91 | return nil, err |
| 92 | } |
| 93 | |
| 94 | // the first request will either succeed or return a 401 |
| 95 | res, err := rt.RoundTrip(first) |
| 96 | if err != nil || res.StatusCode != http.StatusUnauthorized { |
| 97 | return res, err |
| 98 | } |
| 99 | |
| 100 | // drain and close the first message body |
| 101 | _, _ = io.Copy(io.Discard, res.Body) |
| 102 | _ = res.Body.Close() |
| 103 | |
| 104 | // find and cache the challenge |
| 105 | host := req.URL.Hostname() |
| 106 | chal, err := digest.FindChallenge(res.Header) |
| 107 | if err != nil { |
| 108 | // existing cached challenge didn't work, so remove it |
| 109 | da.cacheMu.Lock() |
| 110 | delete(da.cache, host) |
| 111 | da.cacheMu.Unlock() |
| 112 | if err == digest.ErrNoChallenge { |
| 113 | return res, nil |
| 114 | } |
| 115 | return nil, err |
| 116 | } else { |
| 117 | // found new challenge, so cache it |
| 118 | da.cacheMu.Lock() |
| 119 | da.cache[host] = &cchal{c: chal} |
| 120 | da.cacheMu.Unlock() |
| 121 | } |
| 122 | |
| 123 | // make a second copy of the request |
| 124 | second, err := clone() |
| 125 | if err != nil { |
| 126 | return nil, err |
| 127 | } |
| 128 | |
| 129 | // prepare the second request based on the new challenge |
| 130 | if err := da.prepare(second); err != nil { |
| 131 | return nil, err |
| 132 | } |
| 133 | |