package http import ( "crypto/tls" "fmt" "io" "log" "net" "net/http" "net/http/cgi" "net/url" "os" "os/exec" "path/filepath" "strings" "testing" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/transport" fixtures "github.com/go-git/go-git-fixtures/v4" . "gopkg.in/check.v1" ) func Test(t *testing.T) { TestingT(t) } type ClientSuite struct { Endpoint *transport.Endpoint EmptyAuth transport.AuthMethod } var _ = Suite(&ClientSuite{}) func (s *ClientSuite) SetUpSuite(c *C) { var err error s.Endpoint, err = transport.NewEndpoint( "https://github.com/git-fixtures/basic", ) c.Assert(err, IsNil) } func (s *UploadPackSuite) TestNewClient(c *C) { roundTripper := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } cl := &http.Client{Transport: roundTripper} r, ok := NewClient(cl).(*client) c.Assert(ok, Equals, true) c.Assert(r.client, Equals, cl) } func (s *ClientSuite) TestNewBasicAuth(c *C) { a := &BasicAuth{"foo", "qux"} c.Assert(a.Name(), Equals, "http-basic-auth") c.Assert(a.String(), Equals, "http-basic-auth - foo:*******") } func (s *ClientSuite) TestNewTokenAuth(c *C) { a := &TokenAuth{"OAUTH-TOKEN-TEXT"} c.Assert(a.Name(), Equals, "http-token-auth") c.Assert(a.String(), Equals, "http-token-auth - *******") // Check header is set correctly req, err := http.NewRequest("GET", "https://github.com/git-fixtures/basic", nil) c.Assert(err, Equals, nil) a.SetAuth(req) c.Assert(req.Header.Get("Authorization"), Equals, "Bearer OAUTH-TOKEN-TEXT") } func (s *ClientSuite) TestNewErrOK(c *C) { res := &http.Response{StatusCode: http.StatusOK} err := NewErr(res) c.Assert(err, IsNil) } func (s *ClientSuite) TestNewErrUnauthorized(c *C) { s.testNewHTTPError(c, http.StatusUnauthorized, "authentication required") } func (s *ClientSuite) TestNewErrForbidden(c *C) { s.testNewHTTPError(c, http.StatusForbidden, "authorization failed") } func (s *ClientSuite) TestNewErrNotFound(c *C) { s.testNewHTTPError(c, http.StatusNotFound, "repository not found") } func (s *ClientSuite) TestNewHTTPError40x(c *C) { s.testNewHTTPError(c, http.StatusPaymentRequired, "unexpected client error.*") } func (s *ClientSuite) TestNewUnexpectedError(c *C) { res := &http.Response{ StatusCode: 500, Body: io.NopCloser(strings.NewReader("Unexpected error")), } err := NewErr(res) c.Assert(err, NotNil) c.Assert(err, FitsTypeOf, &plumbing.UnexpectedError{}) unexpectedError, _ := err.(*plumbing.UnexpectedError) c.Assert(unexpectedError.Err, FitsTypeOf, &Err{}) httpError, _ := unexpectedError.Err.(*Err) c.Assert(httpError.Reason, Equals, "Unexpected error") } func (s *ClientSuite) Test_newSession(c *C) { cl := NewClientWithOptions(nil, &ClientOptions{ CacheMaxEntries: 2, }).(*client) insecureEP := s.Endpoint insecureEP.InsecureSkipTLS = true session, err := newSession(cl, insecureEP, nil) c.Assert(err, IsNil) sessionTransport := session.client.Transport.(*http.Transport) c.Assert(sessionTransport.TLSClientConfig.InsecureSkipVerify, Equals, true) t, ok := cl.fetchTransport(transportOptions{ insecureSkipTLS: true, }) // transport should be cached. c.Assert(ok, Equals, true) // cached transport should be the one that's used. c.Assert(sessionTransport, Equals, t) caEndpoint := insecureEP caEndpoint.CaBundle = []byte("this is the way") session, err = newSession(cl, caEndpoint, nil) c.Assert(err, IsNil) sessionTransport = session.client.Transport.(*http.Transport) c.Assert(sessionTransport.TLSClientConfig.InsecureSkipVerify, Equals, true) c.Assert(sessionTransport.TLSClientConfig.RootCAs, NotNil) t, ok = cl.fetchTransport(transportOptions{ insecureSkipTLS: true, caBundle: "this is the way", }) // transport should be cached. c.Assert(ok, Equals, true) // cached transport should be the one that's used. c.Assert(sessionTransport, Equals, t) session, err = newSession(cl, caEndpoint, nil) c.Assert(err, IsNil) sessionTransport = session.client.Transport.(*http.Transport) // transport that's going to be used should be cached already. c.Assert(sessionTransport, Equals, t) // no new transport got cached. c.Assert(cl.transports.Len(), Equals, 2) // if the cache does not exist, the transport should still be correctly configured. cl.transports = nil session, err = newSession(cl, insecureEP, nil) c.Assert(err, IsNil) sessionTransport = session.client.Transport.(*http.Transport) c.Assert(sessionTransport.TLSClientConfig.InsecureSkipVerify, Equals, true) } func (s *ClientSuite) testNewHTTPError(c *C, code int, msg string) { req, _ := http.NewRequest("GET", "foo", nil) res := &http.Response{ StatusCode: code, Request: req, } err := NewErr(res) c.Assert(err, NotNil) c.Assert(err, ErrorMatches, msg) } func (s *ClientSuite) TestSetAuth(c *C) { auth := &BasicAuth{} r, err := DefaultClient.NewUploadPackSession(s.Endpoint, auth) c.Assert(err, IsNil) c.Assert(auth, Equals, r.(*upSession).auth) } type mockAuth struct{} func (*mockAuth) Name() string { return "" } func (*mockAuth) String() string { return "" } func (s *ClientSuite) TestSetAuthWrongType(c *C) { _, err := DefaultClient.NewUploadPackSession(s.Endpoint, &mockAuth{}) c.Assert(err, Equals, transport.ErrInvalidAuthMethod) } func (s *ClientSuite) TestModifyEndpointIfRedirect(c *C) { sess := &session{endpoint: nil} u, _ := url.Parse("https://example.com/info/refs") res := &http.Response{Request: &http.Request{URL: u}} c.Assert(func() { sess.ModifyEndpointIfRedirect(res) }, PanicMatches, ".*nil pointer dereference.*") sess = &session{endpoint: nil} // no-op - should return and not panic sess.ModifyEndpointIfRedirect(&http.Response{}) data := []struct { url string endpoint *transport.Endpoint expected *transport.Endpoint }{ {"https://example.com/foo/bar", nil, nil}, {"https://example.com/foo.git/info/refs", &transport.Endpoint{}, &transport.Endpoint{Protocol: "https", Host: "example.com", Path: "/foo.git"}}, {"https://example.com:8080/foo.git/info/refs", &transport.Endpoint{}, &transport.Endpoint{Protocol: "https", Host: "example.com", Port: 8080, Path: "/foo.git"}}, } for _, d := range data { u, _ := url.Parse(d.url) sess := &session{endpoint: d.endpoint} sess.ModifyEndpointIfRedirect(&http.Response{ Request: &http.Request{URL: u}, }) c.Assert(d.endpoint, DeepEquals, d.expected) } } type BaseSuite struct { fixtures.Suite base string host string port int } func (s *BaseSuite) SetUpTest(c *C) { l, err := net.Listen("tcp", "localhost:0") c.Assert(err, IsNil) base, err := os.MkdirTemp(os.TempDir(), fmt.Sprintf("go-git-http-%d", s.port)) c.Assert(err, IsNil) s.port = l.Addr().(*net.TCPAddr).Port s.base = filepath.Join(base, s.host) err = os.MkdirAll(s.base, 0755) c.Assert(err, IsNil) cmd := exec.Command("git", "--exec-path") out, err := cmd.CombinedOutput() c.Assert(err, IsNil) server := &http.Server{ Handler: &cgi.Handler{ Path: filepath.Join(strings.Trim(string(out), "\n"), "git-http-backend"), Env: []string{"GIT_HTTP_EXPORT_ALL=true", fmt.Sprintf("GIT_PROJECT_ROOT=%s", s.base)}, }, } go func() { log.Fatal(server.Serve(l)) }() } func (s *BaseSuite) prepareRepository(c *C, f *fixtures.Fixture, name string) *transport.Endpoint { fs := f.DotGit() err := fixtures.EnsureIsBare(fs) c.Assert(err, IsNil) path := filepath.Join(s.base, name) err = os.Rename(fs.Root(), path) c.Assert(err, IsNil) return s.newEndpoint(c, name) } func (s *BaseSuite) newEndpoint(c *C, name string) *transport.Endpoint { ep, err := transport.NewEndpoint(fmt.Sprintf("http://localhost:%d/%s", s.port, name)) c.Assert(err, IsNil) return ep } func (s *BaseSuite) TearDownTest(c *C) { err := os.RemoveAll(s.base) c.Assert(err, IsNil) }