@@ -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,57 @@ 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
+ let allow_redirects = Arc :: new ( atomic:: AtomicBool :: new ( false ) ) ;
46
+
43
47
// We may error while configuring, which is expected as part of the internal protocol. The error will be
44
48
// received and the sender of the request might restart us.
45
49
let client = reqwest:: blocking:: ClientBuilder :: new ( )
46
50
. connect_timeout ( std:: time:: Duration :: from_secs ( 20 ) )
47
51
. http1_title_case_headers ( )
52
+ . redirect ( reqwest:: redirect:: Policy :: custom ( {
53
+ let allow_redirects = allow_redirects. clone ( ) ;
54
+ move |attempt| {
55
+ if allow_redirects. load ( atomic:: Ordering :: Relaxed ) {
56
+ let curr_url = attempt. url ( ) ;
57
+ let prev_urls = attempt. previous ( ) ;
58
+
59
+ match prev_urls. first ( ) {
60
+ Some ( prev_url) if prev_url. host_str ( ) != curr_url. host_str ( ) => {
61
+ // git does not want to be redirected to a different host.
62
+ attempt. stop ( )
63
+ }
64
+ _ => {
65
+ // emulate default git behaviour which relies on curl default behaviour apparently.
66
+ const CURL_DEFAULT_REDIRS : usize = 50 ;
67
+ if prev_urls. len ( ) >= CURL_DEFAULT_REDIRS {
68
+ attempt. error ( "too many redirects" )
69
+ } else {
70
+ attempt. follow ( )
71
+ }
72
+ }
73
+ }
74
+ } else {
75
+ attempt. stop ( )
76
+ }
77
+ }
78
+ } ) )
48
79
. build ( ) ?;
80
+
49
81
for Request {
50
82
url,
83
+ base_url,
51
84
headers,
52
85
upload_body_kind,
53
86
config,
54
87
} in req_recv
55
88
{
89
+ let effective_url = redirect:: swap_tails ( redirected_base_url. as_deref ( ) , & base_url, url. clone ( ) ) ;
56
90
let mut req_builder = if upload_body_kind. is_some ( ) {
57
- client. post ( url )
91
+ client. post ( & effective_url )
58
92
} else {
59
- client. get ( url )
93
+ client. get ( & effective_url )
60
94
}
61
95
. headers ( headers) ;
62
96
let ( post_body_tx, mut post_body_rx) = pipe:: unidirectional ( 0 ) ;
@@ -91,6 +125,17 @@ impl Default for Remote {
91
125
}
92
126
}
93
127
}
128
+
129
+ let follow = follow. get_or_insert ( config. follow_redirects ) ;
130
+ allow_redirects. store (
131
+ matches ! ( follow, FollowRedirects :: Initial | FollowRedirects :: All ) ,
132
+ atomic:: Ordering :: Relaxed ,
133
+ ) ;
134
+
135
+ if * follow == FollowRedirects :: Initial {
136
+ * follow = FollowRedirects :: None ;
137
+ }
138
+
94
139
let mut res = match client
95
140
. execute ( req)
96
141
. and_then ( reqwest:: blocking:: Response :: error_for_status)
@@ -116,6 +161,11 @@ impl Default for Remote {
116
161
}
117
162
} ;
118
163
164
+ let actual_url = res. url ( ) . as_str ( ) ;
165
+ if actual_url != effective_url. as_str ( ) {
166
+ redirected_base_url = redirect:: base_url ( actual_url, & base_url, url) ?. into ( ) ;
167
+ }
168
+
119
169
let send_headers = {
120
170
let headers = res. headers ( ) ;
121
171
move || -> std:: io:: Result < ( ) > {
@@ -172,7 +222,7 @@ impl Remote {
172
222
fn make_request (
173
223
& mut self ,
174
224
url : & str ,
175
- _base_url : & str ,
225
+ base_url : & str ,
176
226
headers : impl IntoIterator < Item = impl AsRef < str > > ,
177
227
upload_body_kind : Option < PostBodyDataKind > ,
178
228
) -> Result < http:: PostResponse < pipe:: Reader , pipe:: Reader , pipe:: Writer > , http:: Error > {
@@ -197,6 +247,7 @@ impl Remote {
197
247
. request
198
248
. send ( Request {
199
249
url : url. to_owned ( ) ,
250
+ base_url : base_url. to_owned ( ) ,
200
251
headers : header_map,
201
252
upload_body_kind,
202
253
config : self . config . clone ( ) ,
@@ -259,6 +310,7 @@ impl http::Http for Remote {
259
310
260
311
pub ( crate ) struct Request {
261
312
pub url : String ,
313
+ pub base_url : String ,
262
314
pub headers : reqwest:: header:: HeaderMap ,
263
315
pub upload_body_kind : Option < PostBodyDataKind > ,
264
316
pub config : http:: Options ,
0 commit comments