Support redirects for libgit2 managed transport
For backwards compatibility, support for HTTP redirection is enabled when targeting the same host, and no TLS downgrade took place. Signed-off-by: Paulo Gomes <paulo.gomes@weave.works>
This commit is contained in:
		
							parent
							
								
									43661dd15e
								
							
						
					
					
						commit
						115040e9ea
					
				|  | @ -44,12 +44,12 @@ THE SOFTWARE. | ||||||
| package managed | package managed | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"bytes" | ||||||
| 	"crypto/tls" | 	"crypto/tls" | ||||||
| 	"crypto/x509" | 	"crypto/x509" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"io/ioutil" |  | ||||||
| 	"net" | 	"net" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
|  | @ -133,6 +133,25 @@ func (t *httpSmartSubtransport) Action(targetUrl string, action git2go.SmartServ | ||||||
| 		stream.sendRequestBackground() | 		stream.sendRequestBackground() | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	client.CheckRedirect = func(req *http.Request, via []*http.Request) error { | ||||||
|  | 		if len(via) >= 3 { | ||||||
|  | 			return fmt.Errorf("too many redirects") | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// golang will change POST to GET in case of redirects.
 | ||||||
|  | 		if len(via) >= 0 && req.Method != via[0].Method { | ||||||
|  | 			if via[0].URL.Scheme == "https" && req.URL.Scheme == "http" { | ||||||
|  | 				return fmt.Errorf("downgrade from https to http is not allowed: from %q to %q", via[0].URL.String(), req.URL.String()) | ||||||
|  | 			} | ||||||
|  | 			if via[0].URL.Host != req.URL.Host { | ||||||
|  | 				return fmt.Errorf("cross hosts redirects are not allowed: from %s to %s", via[0].URL.Host, req.URL.Host) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			return http.ErrUseLastResponse | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	return stream, nil | 	return stream, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -165,7 +184,10 @@ func createClientRequest(targetUrl string, action git2go.SmartServiceAction, t * | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	client := &http.Client{Transport: t, Timeout: fullHttpClientTimeOut} | 	client := &http.Client{ | ||||||
|  | 		Transport: t, | ||||||
|  | 		Timeout:   fullHttpClientTimeOut, | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	switch action { | 	switch action { | ||||||
| 	case git2go.SmartServiceActionUploadpackLs: | 	case git2go.SmartServiceActionUploadpackLs: | ||||||
|  | @ -218,6 +240,7 @@ type httpSmartSubtransportStream struct { | ||||||
| 	recvReply   sync.WaitGroup | 	recvReply   sync.WaitGroup | ||||||
| 	httpError   error | 	httpError   error | ||||||
| 	m           sync.RWMutex | 	m           sync.RWMutex | ||||||
|  | 	targetURL   string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func newManagedHttpStream(owner *httpSmartSubtransport, req *http.Request, client *http.Client) *httpSmartSubtransportStream { | func newManagedHttpStream(owner *httpSmartSubtransport, req *http.Request, client *http.Client) *httpSmartSubtransportStream { | ||||||
|  | @ -246,18 +269,21 @@ func (self *httpSmartSubtransportStream) Read(buf []byte) (int, error) { | ||||||
| 	self.recvReply.Wait() | 	self.recvReply.Wait() | ||||||
| 
 | 
 | ||||||
| 	self.m.RLock() | 	self.m.RLock() | ||||||
| 	defer self.m.RUnlock() | 	err := self.httpError | ||||||
| 	if self.httpError != nil { | 	self.m.RUnlock() | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
| 		return 0, self.httpError | 		return 0, self.httpError | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
| 	return self.resp.Body.Read(buf) | 	return self.resp.Body.Read(buf) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (self *httpSmartSubtransportStream) Write(buf []byte) (int, error) { | func (self *httpSmartSubtransportStream) Write(buf []byte) (int, error) { | ||||||
| 	self.m.RLock() | 	self.m.RLock() | ||||||
| 	defer self.m.RUnlock() | 	err := self.httpError | ||||||
| 	if self.httpError != nil { | 	self.m.RUnlock() | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
| 		return 0, self.httpError | 		return 0, self.httpError | ||||||
| 	} | 	} | ||||||
| 	return self.writer.Write(buf) | 	return self.writer.Write(buf) | ||||||
|  | @ -308,29 +334,53 @@ func (self *httpSmartSubtransportStream) sendRequest() error { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	req := &http.Request{ | 	var content []byte | ||||||
| 		Method: self.req.Method, | 	for { | ||||||
| 		URL:    self.req.URL, | 		req := &http.Request{ | ||||||
| 		Header: self.req.Header, | 			Method: self.req.Method, | ||||||
| 	} | 			URL:    self.req.URL, | ||||||
| 	if req.Method == "POST" { | 			Header: self.req.Header, | ||||||
| 		req.Body = self.reader | 		} | ||||||
| 		req.ContentLength = -1 | 		if req.Method == "POST" { | ||||||
|  | 			if len(content) == 0 { | ||||||
|  | 				// a copy of the request body needs to be saved so
 | ||||||
|  | 				// it can be reused in case of redirects.
 | ||||||
|  | 				if content, err = io.ReadAll(self.reader); err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			req.Body = io.NopCloser(bytes.NewReader(content)) | ||||||
|  | 			req.ContentLength = -1 | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		req.SetBasicAuth(userName, password) | ||||||
|  | 		resp, err = self.client.Do(req) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// GET requests will be automatically redirected.
 | ||||||
|  | 		// POST require the new destination, and also the body content.
 | ||||||
|  | 		if req.Method == "POST" && resp.StatusCode >= 301 && resp.StatusCode <= 308 { | ||||||
|  | 			// The next try will go against the new destination
 | ||||||
|  | 			self.req.URL, err = resp.Location() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if resp.StatusCode == http.StatusOK { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		io.Copy(io.Discard, resp.Body) | ||||||
|  | 		defer resp.Body.Close() | ||||||
|  | 		return fmt.Errorf("Unhandled HTTP error %s", resp.Status) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	req.SetBasicAuth(userName, password) | 	self.resp = resp | ||||||
| 	resp, err = self.client.Do(req) | 	self.sentRequest = true | ||||||
| 	if err != nil { | 	return nil | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if resp.StatusCode == http.StatusOK { |  | ||||||
| 		self.resp = resp |  | ||||||
| 		self.sentRequest = true |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	io.Copy(ioutil.Discard, resp.Body) |  | ||||||
| 	defer resp.Body.Close() |  | ||||||
| 	return fmt.Errorf("Unhandled HTTP error %s", resp.Status) |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -304,3 +304,24 @@ func TestManagedTransport_E2E(t *testing.T) { | ||||||
| 	g.Expect(err).ToNot(HaveOccurred()) | 	g.Expect(err).ToNot(HaveOccurred()) | ||||||
| 	repo.Free() | 	repo.Free() | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func TestManagedTransport_HandleRedirect(t *testing.T) { | ||||||
|  | 	g := NewWithT(t) | ||||||
|  | 
 | ||||||
|  | 	tmpDir, _ := os.MkdirTemp("", "test") | ||||||
|  | 	defer os.RemoveAll(tmpDir) | ||||||
|  | 
 | ||||||
|  | 	// Force managed transport to be enabled
 | ||||||
|  | 	InitManagedTransport() | ||||||
|  | 
 | ||||||
|  | 	// GitHub will cause a 301 and redirect to https
 | ||||||
|  | 	repo, err := git2go.Clone("http://github.com/stefanprodan/podinfo", tmpDir, &git2go.CloneOptions{ | ||||||
|  | 		FetchOptions: git2go.FetchOptions{}, | ||||||
|  | 		CheckoutOptions: git2go.CheckoutOptions{ | ||||||
|  | 			Strategy: git2go.CheckoutForce, | ||||||
|  | 		}, | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	g.Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 	repo.Free() | ||||||
|  | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue