@@ -2,16 +2,13 @@ package req
22
33import (
44 "crypto/md5"
5- "crypto/rand"
65 "crypto/sha256"
76 "crypto/sha512"
87 "errors"
9- "fmt"
108 "hash"
11- "io"
129 "net/http"
13- "strings"
1410
11+ "github.com/icholy/digest"
1512 "github.com/imroc/req/v3/internal/header"
1613)
1714
@@ -38,7 +35,7 @@ func handleDigestAuthFunc(username, password string) ResponseMiddleware {
3835 if resp .Err != nil || resp .StatusCode != http .StatusUnauthorized {
3936 return nil
4037 }
41- auth , err := createDigestAuth (resp .Response , username , password )
38+ auth , err := createDigestAuth (resp .Request . RawRequest , resp . Response , username , password )
4239 if err != nil {
4340 return err
4441 }
@@ -62,217 +59,26 @@ func handleDigestAuthFunc(username, password string) ResponseMiddleware {
6259 req .Header = make (http.Header )
6360 }
6461 req .Header .Set (header .Authorization , auth )
65- resp .Response , err = client .GetTransport (). RoundTrip (& req )
62+ resp .Response , err = client .httpClient . Do (& req )
6663 return err
6764 }
6865}
6966
70- func createDigestAuth (resp * http.Response , username , password string ) (auth string , err error ) {
71- chal := resp .Header .Get (header .WwwAuthenticate )
72- if chal == "" {
73- return "" , errDigestBadChallenge
74- }
75-
76- c , err := parseChallenge (chal )
67+ func createDigestAuth (req * http.Request , resp * http.Response , username , password string ) (auth string , err error ) {
68+ chal , err := digest .FindChallenge (resp .Header )
7769 if err != nil {
7870 return "" , err
7971 }
80-
81- // Form credentials based on the challenge
82- cr := newCredentials (resp .Request .URL .RequestURI (), resp .Request .Method , username , password , c )
83- auth , err = cr .authorize ()
84- return
85- }
86-
87- func newCredentials (digestURI , method , username , password string , c * challenge ) * credentials {
88- return & credentials {
89- username : username ,
90- userhash : c .userhash ,
91- realm : c .realm ,
92- nonce : c .nonce ,
93- digestURI : digestURI ,
94- algorithm : c .algorithm ,
95- sessionAlg : strings .HasSuffix (c .algorithm , "-sess" ),
96- opaque : c .opaque ,
97- messageQop : c .qop ,
98- nc : 0 ,
99- method : method ,
100- password : password ,
101- }
102- }
103-
104- type challenge struct {
105- realm string
106- domain string
107- nonce string
108- opaque string
109- stale string
110- algorithm string
111- qop string
112- userhash string
113- }
114-
115- func parseChallenge (input string ) (* challenge , error ) {
116- const ws = " \n \r \t "
117- const qs = `"`
118- s := strings .Trim (input , ws )
119- if ! strings .HasPrefix (s , "Digest " ) {
120- return nil , errDigestBadChallenge
121- }
122- s = strings .Trim (s [7 :], ws )
123- sl := strings .Split (s , "," )
124- c := & challenge {}
125- var r []string
126- for i := range sl {
127- r = strings .SplitN (strings .TrimSpace (sl [i ]), "=" , 2 )
128- if len (r ) != 2 {
129- return nil , errDigestBadChallenge
130- }
131- switch r [0 ] {
132- case "realm" :
133- c .realm = strings .Trim (r [1 ], qs )
134- case "domain" :
135- c .domain = strings .Trim (r [1 ], qs )
136- case "nonce" :
137- c .nonce = strings .Trim (r [1 ], qs )
138- case "opaque" :
139- c .opaque = strings .Trim (r [1 ], qs )
140- case "stale" :
141- c .stale = strings .Trim (r [1 ], qs )
142- case "algorithm" :
143- c .algorithm = strings .Trim (r [1 ], qs )
144- case "qop" :
145- c .qop = strings .Trim (r [1 ], qs )
146- case "charset" :
147- if strings .ToUpper (strings .Trim (r [1 ], qs )) != "UTF-8" {
148- return nil , errDigestCharset
149- }
150- case "userhash" :
151- c .userhash = strings .Trim (r [1 ], qs )
152- default :
153- return nil , errDigestBadChallenge
154- }
155- }
156- return c , nil
157- }
158-
159- type credentials struct {
160- username string
161- userhash string
162- realm string
163- nonce string
164- digestURI string
165- algorithm string
166- sessionAlg bool
167- cNonce string
168- opaque string
169- messageQop string
170- nc int
171- method string
172- password string
173- }
174-
175- func (c * credentials ) authorize () (string , error ) {
176- if _ , ok := hashFuncs [c .algorithm ]; ! ok {
177- return "" , errDigestAlgNotSupported
178- }
179-
180- if err := c .validateQop (); err != nil {
181- return "" , err
182- }
183-
184- resp , err := c .resp ()
72+ cred , err := digest .Digest (chal , digest.Options {
73+ Username : username ,
74+ Password : password ,
75+ Method : req .Method ,
76+ URI : req .URL .RequestURI (),
77+ GetBody : req .GetBody ,
78+ Count : 1 ,
79+ })
18580 if err != nil {
18681 return "" , err
18782 }
188-
189- sl := make ([]string , 0 , 10 )
190- if c .userhash == "true" {
191- // RFC 7616 3.4.4
192- c .username = c .h (fmt .Sprintf ("%s:%s" , c .username , c .realm ))
193- sl = append (sl , fmt .Sprintf (`userhash=%s` , c .userhash ))
194- }
195- sl = append (sl , fmt .Sprintf (`username="%s"` , c .username ))
196- sl = append (sl , fmt .Sprintf (`realm="%s"` , c .realm ))
197- sl = append (sl , fmt .Sprintf (`nonce="%s"` , c .nonce ))
198- sl = append (sl , fmt .Sprintf (`uri="%s"` , c .digestURI ))
199- sl = append (sl , fmt .Sprintf (`response="%s"` , resp ))
200- sl = append (sl , fmt .Sprintf (`algorithm=%s` , c .algorithm ))
201- if c .opaque != "" {
202- sl = append (sl , fmt .Sprintf (`opaque="%s"` , c .opaque ))
203- }
204- if c .messageQop != "" {
205- sl = append (sl , fmt .Sprintf ("qop=%s" , c .messageQop ))
206- sl = append (sl , fmt .Sprintf ("nc=%08x" , c .nc ))
207- sl = append (sl , fmt .Sprintf (`cnonce="%s"` , c .cNonce ))
208- }
209-
210- return fmt .Sprintf ("Digest %s" , strings .Join (sl , ", " )), nil
211- }
212-
213- func (c * credentials ) validateQop () error {
214- // Currently only supporting auth quality of protection. TODO: add auth-int support
215- if c .messageQop == "" {
216- return nil
217- }
218- possibleQops := strings .Split (c .messageQop , ", " )
219- var authSupport bool
220- for _ , qop := range possibleQops {
221- if qop == "auth" {
222- authSupport = true
223- break
224- }
225- }
226- if ! authSupport {
227- return errDigestQopNotSupported
228- }
229-
230- return nil
231- }
232-
233- func (c * credentials ) h (data string ) string {
234- hfCtor := hashFuncs [c .algorithm ]
235- hf := hfCtor ()
236- _ , _ = hf .Write ([]byte (data )) // Hash.Write never returns an error
237- return fmt .Sprintf ("%x" , hf .Sum (nil ))
238- }
239-
240- func (c * credentials ) resp () (string , error ) {
241- c .nc ++
242-
243- b := make ([]byte , 16 )
244- _ , err := io .ReadFull (rand .Reader , b )
245- if err != nil {
246- return "" , err
247- }
248- c .cNonce = fmt .Sprintf ("%x" , b )[:32 ]
249-
250- ha1 := c .ha1 ()
251- ha2 := c .ha2 ()
252-
253- if len (c .messageQop ) == 0 {
254- return c .h (fmt .Sprintf ("%s:%s:%s" , ha1 , c .nonce , ha2 )), nil
255- }
256- return c .kd (ha1 , fmt .Sprintf ("%s:%08x:%s:%s:%s" ,
257- c .nonce , c .nc , c .cNonce , c .messageQop , ha2 )), nil
258- }
259-
260- func (c * credentials ) kd (secret , data string ) string {
261- return c .h (fmt .Sprintf ("%s:%s" , secret , data ))
262- }
263-
264- // RFC 7616 3.4.2
265- func (c * credentials ) ha1 () string {
266- ret := c .h (fmt .Sprintf ("%s:%s:%s" , c .username , c .realm , c .password ))
267- if c .sessionAlg {
268- return c .h (fmt .Sprintf ("%s:%s:%s" , ret , c .nonce , c .cNonce ))
269- }
270-
271- return ret
272- }
273-
274- // RFC 7616 3.4.3
275- func (c * credentials ) ha2 () string {
276- // currently no auth-int support
277- return c .h (fmt .Sprintf ("%s:%s" , c .method , c .digestURI ))
83+ return cred .String (), nil
27884}
0 commit comments