@@ -3,14 +3,12 @@ use std::{
3
3
convert:: TryFrom ,
4
4
io:: { Read , Write } ,
5
5
str:: FromStr ,
6
+ sync:: { atomic, Arc } ,
6
7
} ;
7
8
8
9
use gix_features:: io:: pipe;
9
10
10
- use crate :: client:: {
11
- http,
12
- http:: { reqwest:: Remote , traits:: PostBodyDataKind } ,
13
- } ;
11
+ use crate :: client:: http:: { self , options:: FollowRedirects , redirect, reqwest:: Remote , traits:: PostBodyDataKind } ;
14
12
15
13
/// The error returned by the 'remote' helper, a purely internal construct to perform http requests.
16
14
#[ derive( Debug , thiserror:: Error ) ]
@@ -22,6 +20,8 @@ pub enum Error {
22
20
ReadPostBody ( #[ from] std:: io:: Error ) ,
23
21
#[ error( "Request configuration failed" ) ]
24
22
ConfigureRequest ( #[ from] Box < dyn std:: error:: Error + Send + Sync + ' static > ) ,
23
+ #[ error( transparent) ]
24
+ Redirect ( #[ from] redirect:: Error ) ,
25
25
}
26
26
27
27
impl crate :: IsSpuriousError for Error {
@@ -40,23 +40,51 @@ impl Default for Remote {
40
40
let ( req_send, req_recv) = std:: sync:: mpsc:: sync_channel ( 0 ) ;
41
41
let ( res_send, res_recv) = std:: sync:: mpsc:: sync_channel ( 0 ) ;
42
42
let handle = std:: thread:: spawn ( move || -> Result < ( ) , Error > {
43
+ let mut follow = None ;
44
+ let mut redirected_base_url = None :: < String > ;
45
+
46
+ // This initial value is just a place holder, it will get modified
47
+ // before `client.execute` is called.
48
+ let current_follow_policy = Arc :: new ( atomic:: AtomicBool :: new ( false ) ) ;
49
+
50
+ let follow_policy = Arc :: clone ( & current_follow_policy) ;
51
+
43
52
// We may error while configuring, which is expected as part of the internal protocol. The error will be
44
53
// received and the sender of the request might restart us.
45
54
let client = reqwest:: blocking:: ClientBuilder :: new ( )
46
55
. connect_timeout ( std:: time:: Duration :: from_secs ( 20 ) )
47
56
. http1_title_case_headers ( )
57
+ . redirect ( reqwest:: redirect:: Policy :: custom ( move |attempt| {
58
+ if follow_policy. load ( atomic:: Ordering :: Relaxed ) {
59
+ let curr_url = attempt. url ( ) ;
60
+ let prev_urls = attempt. previous ( ) ;
61
+
62
+ match prev_urls. first ( ) {
63
+ Some ( prev_url) if prev_url. host_str ( ) != curr_url. host_str ( ) => {
64
+ // gix does not want to be redirected to a different host.
65
+ attempt. stop ( )
66
+ }
67
+ _ => attempt. follow ( ) ,
68
+ }
69
+ } else {
70
+ attempt. stop ( )
71
+ }
72
+ } ) )
48
73
. build ( ) ?;
74
+
49
75
for Request {
50
76
url,
77
+ base_url,
51
78
headers,
52
79
upload_body_kind,
53
80
config,
54
81
} in req_recv
55
82
{
83
+ let effective_url = redirect:: swap_tails ( redirected_base_url. as_deref ( ) , & base_url, url. clone ( ) ) ;
56
84
let mut req_builder = if upload_body_kind. is_some ( ) {
57
- client. post ( url )
85
+ client. post ( & effective_url )
58
86
} else {
59
- client. get ( url )
87
+ client. get ( & effective_url )
60
88
}
61
89
. headers ( headers) ;
62
90
let ( post_body_tx, mut post_body_rx) = pipe:: unidirectional ( 0 ) ;
@@ -91,6 +119,21 @@ impl Default for Remote {
91
119
}
92
120
}
93
121
}
122
+
123
+ let follow = follow. get_or_insert ( config. follow_redirects ) ;
124
+
125
+ current_follow_policy. store (
126
+ match * follow {
127
+ FollowRedirects :: None => false ,
128
+ FollowRedirects :: Initial | FollowRedirects :: All => true ,
129
+ } ,
130
+ atomic:: Ordering :: Relaxed ,
131
+ ) ;
132
+
133
+ if * follow == FollowRedirects :: Initial {
134
+ * follow = FollowRedirects :: None ;
135
+ }
136
+
94
137
let mut res = match client
95
138
. execute ( req)
96
139
. and_then ( reqwest:: blocking:: Response :: error_for_status)
@@ -116,6 +159,11 @@ impl Default for Remote {
116
159
}
117
160
} ;
118
161
162
+ let actual_url = res. url ( ) . as_str ( ) ;
163
+ if actual_url != effective_url. as_str ( ) {
164
+ redirected_base_url = redirect:: base_url ( actual_url, & base_url, url) ?. into ( ) ;
165
+ }
166
+
119
167
let send_headers = {
120
168
let headers = res. headers ( ) ;
121
169
move || -> std:: io:: Result < ( ) > {
@@ -172,7 +220,7 @@ impl Remote {
172
220
fn make_request (
173
221
& mut self ,
174
222
url : & str ,
175
- _base_url : & str ,
223
+ base_url : & str ,
176
224
headers : impl IntoIterator < Item = impl AsRef < str > > ,
177
225
upload_body_kind : Option < PostBodyDataKind > ,
178
226
) -> Result < http:: PostResponse < pipe:: Reader , pipe:: Reader , pipe:: Writer > , http:: Error > {
@@ -197,6 +245,7 @@ impl Remote {
197
245
. request
198
246
. send ( Request {
199
247
url : url. to_owned ( ) ,
248
+ base_url : base_url. to_owned ( ) ,
200
249
headers : header_map,
201
250
upload_body_kind,
202
251
config : self . config . clone ( ) ,
@@ -259,6 +308,7 @@ impl http::Http for Remote {
259
308
260
309
pub ( crate ) struct Request {
261
310
pub url : String ,
311
+ pub base_url : String ,
262
312
pub headers : reqwest:: header:: HeaderMap ,
263
313
pub upload_body_kind : Option < PostBodyDataKind > ,
264
314
pub config : http:: Options ,
0 commit comments