1
1
use crate :: soap:: client:: Credentials ;
2
+ use nonzero_ext:: nonzero;
2
3
use reqwest:: { RequestBuilder , Response } ;
3
4
use std:: fmt:: { Debug , Formatter } ;
5
+ use std:: num:: NonZeroU8 ;
4
6
use thiserror:: Error ;
5
7
use url:: Url ;
6
8
@@ -22,8 +24,10 @@ pub struct Digest {
22
24
23
25
enum State {
24
26
Default ,
25
- Got401 ( reqwest:: Response ) ,
26
- Got401Twice ,
27
+ Got401 {
28
+ response : Response ,
29
+ count : NonZeroU8 ,
30
+ } ,
27
31
}
28
32
29
33
impl Digest {
@@ -37,29 +41,55 @@ impl Digest {
37
41
}
38
42
39
43
impl Digest {
44
+ /// Call this when the authentication was successful.
45
+ pub fn set_success ( & mut self ) {
46
+ if let State :: Got401 { count, .. } = & mut self . state {
47
+ // We always store at least one request, so it's never zero.
48
+ * count = nonzero ! ( 1_u8 ) ;
49
+ }
50
+ }
51
+
52
+ /// Call this when received 401 Unauthorized.
40
53
pub fn set_401 ( & mut self , response : Response ) {
41
- match self . state {
42
- State :: Default => self . state = State :: Got401 ( response) ,
43
- State :: Got401 ( _) => self . state = State :: Got401Twice ,
44
- State :: Got401Twice => { }
54
+ self . state = match self . state {
55
+ State :: Default => State :: Got401 {
56
+ response,
57
+ count : nonzero ! ( 1_u8 ) ,
58
+ } ,
59
+ State :: Got401 { count, .. } => State :: Got401 {
60
+ response,
61
+ count : count. saturating_add ( 1 ) ,
62
+ } ,
45
63
}
46
64
}
47
65
48
66
pub fn is_failed ( & self ) -> bool {
49
- matches ! ( self . state, State :: Got401Twice )
67
+ match & self . state {
68
+ State :: Default => false ,
69
+ // Possible scenarios:
70
+ // - We've got 401 with a challenge for the first time, we calculate the answer, then
71
+ // we get 200 OK. So, a single 401 is never a failure.
72
+ // - After successful auth the count is 1 because we always store at least one request,
73
+ // and the caller decided to reuse the same challenge for multiple requests. But at
74
+ // some point, we'll get a 401 with a new challenge and `stale=true`.
75
+ // So, we'll get a second 401, and this is also not a failure because after
76
+ // calculating the answer to the challenge, we'll get a 200 OK, and will reset the
77
+ // counter in `set_success()`.
78
+ // - Three 401's in a row is certainly a failure.
79
+ State :: Got401 { count, .. } => count. get ( ) >= 3 ,
80
+ }
50
81
}
51
82
52
83
pub fn add_headers ( & self , mut request : RequestBuilder ) -> Result < RequestBuilder , Error > {
53
84
match & self . state {
54
85
State :: Default => Ok ( request) ,
55
- State :: Got401 ( response) => {
86
+ State :: Got401 { response, .. } => {
56
87
let creds = self . creds . as_ref ( ) . ok_or ( Error :: NoCredentials ) ?;
57
88
58
89
request = request. header ( "Authorization" , digest_auth ( response, creds, & self . uri ) ?) ;
59
90
60
91
Ok ( request)
61
92
}
62
- State :: Got401Twice => Err ( Error :: InvalidState ) ,
63
93
}
64
94
}
65
95
}
@@ -94,10 +124,11 @@ impl Debug for Digest {
94
124
95
125
impl Debug for State {
96
126
fn fmt ( & self , f : & mut Formatter < ' _ > ) -> std:: fmt:: Result {
97
- f. write_str ( match self {
98
- State :: Default => "FirstRequest" ,
99
- State :: Got401 ( _) => "Got401" ,
100
- State :: Got401Twice => "Got401Twice" ,
101
- } )
127
+ match self {
128
+ State :: Default => write ! ( f, "FirstRequest" ) ?,
129
+ State :: Got401 { count, .. } => write ! ( f, "Got401({count})" ) ?,
130
+ } ;
131
+
132
+ Ok ( ( ) )
102
133
}
103
134
}
0 commit comments