@@ -44,12 +44,12 @@ THE SOFTWARE.
44
44
package managed
45
45
46
46
import (
47
+ "bytes"
47
48
"crypto/tls"
48
49
"crypto/x509"
49
50
"errors"
50
51
"fmt"
51
52
"io"
52
- "io/ioutil"
53
53
"net"
54
54
"net/http"
55
55
"net/url"
@@ -133,6 +133,25 @@ func (t *httpSmartSubtransport) Action(targetUrl string, action git2go.SmartServ
133
133
stream .sendRequestBackground ()
134
134
}
135
135
136
+ client .CheckRedirect = func (req * http.Request , via []* http.Request ) error {
137
+ if len (via ) >= 3 {
138
+ return fmt .Errorf ("too many redirects" )
139
+ }
140
+
141
+ // golang will change POST to GET in case of redirects.
142
+ if len (via ) >= 0 && req .Method != via [0 ].Method {
143
+ if via [0 ].URL .Scheme == "https" && req .URL .Scheme == "http" {
144
+ return fmt .Errorf ("downgrade from https to http is not allowed: from %q to %q" , via [0 ].URL .String (), req .URL .String ())
145
+ }
146
+ if via [0 ].URL .Host != req .URL .Host {
147
+ return fmt .Errorf ("cross hosts redirects are not allowed: from %s to %s" , via [0 ].URL .Host , req .URL .Host )
148
+ }
149
+
150
+ return http .ErrUseLastResponse
151
+ }
152
+ return nil
153
+ }
154
+
136
155
return stream , nil
137
156
}
138
157
@@ -165,7 +184,10 @@ func createClientRequest(targetUrl string, action git2go.SmartServiceAction, t *
165
184
}
166
185
}
167
186
168
- client := & http.Client {Transport : t , Timeout : fullHttpClientTimeOut }
187
+ client := & http.Client {
188
+ Transport : t ,
189
+ Timeout : fullHttpClientTimeOut ,
190
+ }
169
191
170
192
switch action {
171
193
case git2go .SmartServiceActionUploadpackLs :
@@ -218,6 +240,7 @@ type httpSmartSubtransportStream struct {
218
240
recvReply sync.WaitGroup
219
241
httpError error
220
242
m sync.RWMutex
243
+ targetURL string
221
244
}
222
245
223
246
func newManagedHttpStream (owner * httpSmartSubtransport , req * http.Request , client * http.Client ) * httpSmartSubtransportStream {
@@ -246,18 +269,21 @@ func (self *httpSmartSubtransportStream) Read(buf []byte) (int, error) {
246
269
self .recvReply .Wait ()
247
270
248
271
self .m .RLock ()
249
- defer self .m .RUnlock ()
250
- if self .httpError != nil {
272
+ err := self .httpError
273
+ self .m .RUnlock ()
274
+
275
+ if err != nil {
251
276
return 0 , self .httpError
252
277
}
253
-
254
278
return self .resp .Body .Read (buf )
255
279
}
256
280
257
281
func (self * httpSmartSubtransportStream ) Write (buf []byte ) (int , error ) {
258
282
self .m .RLock ()
259
- defer self .m .RUnlock ()
260
- if self .httpError != nil {
283
+ err := self .httpError
284
+ self .m .RUnlock ()
285
+
286
+ if err != nil {
261
287
return 0 , self .httpError
262
288
}
263
289
return self .writer .Write (buf )
@@ -308,29 +334,53 @@ func (self *httpSmartSubtransportStream) sendRequest() error {
308
334
}
309
335
}
310
336
311
- req := & http.Request {
312
- Method : self .req .Method ,
313
- URL : self .req .URL ,
314
- Header : self .req .Header ,
315
- }
316
- if req .Method == "POST" {
317
- req .Body = self .reader
318
- req .ContentLength = - 1
319
- }
337
+ var content []byte
338
+ for {
339
+ req := & http.Request {
340
+ Method : self .req .Method ,
341
+ URL : self .req .URL ,
342
+ Header : self .req .Header ,
343
+ }
344
+ if req .Method == "POST" {
345
+ if len (content ) == 0 {
346
+ // a copy of the request body needs to be saved so
347
+ // it can be reused in case of redirects.
348
+ if content , err = io .ReadAll (self .reader ); err != nil {
349
+ return err
350
+ }
351
+ }
352
+ req .Body = io .NopCloser (bytes .NewReader (content ))
353
+ req .ContentLength = - 1
354
+ }
320
355
321
- req .SetBasicAuth (userName , password )
322
- resp , err = self .client .Do (req )
323
- if err != nil {
324
- return err
325
- }
356
+ req .SetBasicAuth (userName , password )
357
+ resp , err = self .client .Do (req )
358
+ if err != nil {
359
+ return err
360
+ }
326
361
327
- if resp .StatusCode == http .StatusOK {
328
- self .resp = resp
329
- self .sentRequest = true
330
- return nil
362
+ // GET requests will be automatically redirected.
363
+ // POST require the new destination, and also the body content.
364
+ if req .Method == "POST" && resp .StatusCode >= 301 && resp .StatusCode <= 308 {
365
+ // The next try will go against the new destination
366
+ self .req .URL , err = resp .Location ()
367
+ if err != nil {
368
+ return err
369
+ }
370
+
371
+ continue
372
+ }
373
+
374
+ if resp .StatusCode == http .StatusOK {
375
+ break
376
+ }
377
+
378
+ io .Copy (io .Discard , resp .Body )
379
+ defer resp .Body .Close ()
380
+ return fmt .Errorf ("Unhandled HTTP error %s" , resp .Status )
331
381
}
332
382
333
- io . Copy ( ioutil . Discard , resp . Body )
334
- defer resp . Body . Close ()
335
- return fmt . Errorf ( "Unhandled HTTP error %s" , resp . Status )
383
+ self . resp = resp
384
+ self . sentRequest = true
385
+ return nil
336
386
}
0 commit comments