// Package http implements the HTTP transport protocol. package http import ( "bytes" "fmt" "net/http" "strconv" "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp" "gopkg.in/src-d/go-git.v4/plumbing/transport" ) // it requires a bytes.Buffer, because we need to know the length func applyHeadersToRequest(req *http.Request, content *bytes.Buffer, host string, requestType string) { req.Header.Add("User-Agent", "git/1.0") req.Header.Add("Host", host) // host:port if content == nil { req.Header.Add("Accept", "*/*") return } req.Header.Add("Accept", fmt.Sprintf("application/x-%s-result", requestType)) req.Header.Add("Content-Type", fmt.Sprintf("application/x-%s-request", requestType)) req.Header.Add("Content-Length", strconv.Itoa(content.Len())) } func advertisedReferences(s *session, serviceName string) (*packp.AdvRefs, error) { url := fmt.Sprintf( "%s/info/refs?service=%s", s.endpoint.String(), serviceName, ) req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err } s.applyAuthToRequest(req) applyHeadersToRequest(req, nil, s.endpoint.Host, serviceName) res, err := s.client.Do(req) if err != nil { return nil, err } if err := NewErr(res); err != nil { _ = res.Body.Close() return nil, err } ar := packp.NewAdvRefs() if err := ar.Decode(res.Body); err != nil { if err == packp.ErrEmptyAdvRefs { err = transport.ErrEmptyRemoteRepository } return nil, err } transport.FilterUnsupportedCapabilities(ar.Capabilities) s.advRefs = ar return ar, nil } type client struct { c *http.Client } // DefaultClient is the default HTTP client, which uses `http.DefaultClient`. var DefaultClient = NewClient(nil) // NewClient creates a new client with a custom net/http client. // See `InstallProtocol` to install and override default http client. // Unless a properly initialized client is given, it will fall back into // `http.DefaultClient`. // // Note that for HTTP client cannot distinguist between private repositories and // unexistent repositories on GitHub. So it returns `ErrAuthorizationRequired` // for both. func NewClient(c *http.Client) transport.Transport { if c == nil { return &client{http.DefaultClient} } return &client{ c: c, } } func (c *client) NewUploadPackSession(ep *transport.Endpoint, auth transport.AuthMethod) ( transport.UploadPackSession, error) { return newUploadPackSession(c.c, ep, auth) } func (c *client) NewReceivePackSession(ep *transport.Endpoint, auth transport.AuthMethod) ( transport.ReceivePackSession, error) { return newReceivePackSession(c.c, ep, auth) } type session struct { auth AuthMethod client *http.Client endpoint *transport.Endpoint advRefs *packp.AdvRefs } func newSession(c *http.Client, ep *transport.Endpoint, auth transport.AuthMethod) (*session, error) { s := &session{ auth: basicAuthFromEndpoint(ep), client: c, endpoint: ep, } if auth != nil { a, ok := auth.(AuthMethod) if !ok { return nil, transport.ErrInvalidAuthMethod } s.auth = a } return s, nil } func (*session) Close() error { return nil } func (s *session) applyAuthToRequest(req *http.Request) { if s.auth == nil { return } s.auth.setAuth(req) } // AuthMethod is concrete implementation of common.AuthMethod for HTTP services type AuthMethod interface { transport.AuthMethod setAuth(r *http.Request) } func basicAuthFromEndpoint(ep *transport.Endpoint) *BasicAuth { u := ep.User if u == "" { return nil } return &BasicAuth{u, ep.Password} } // BasicAuth represent a HTTP basic auth type BasicAuth struct { Username, Password string } func (a *BasicAuth) setAuth(r *http.Request) { if a == nil { return } r.SetBasicAuth(a.Username, a.Password) } // Name is name of the auth func (a *BasicAuth) Name() string { return "http-basic-auth" } func (a *BasicAuth) String() string { masked := "*******" if a.Password == "" { masked = "" } return fmt.Sprintf("%s - %s:%s", a.Name(), a.Username, masked) } // Err is a dedicated error to return errors based on status code type Err struct { Response *http.Response } // NewErr returns a new Err based on a http response func NewErr(r *http.Response) error { if r.StatusCode >= http.StatusOK && r.StatusCode < http.StatusMultipleChoices { return nil } switch r.StatusCode { case http.StatusUnauthorized: return transport.ErrAuthenticationRequired case http.StatusForbidden: return transport.ErrAuthorizationFailed case http.StatusNotFound: return transport.ErrRepositoryNotFound } return plumbing.NewUnexpectedError(&Err{r}) } // StatusCode returns the status code of the response func (e *Err) StatusCode() int { return e.Response.StatusCode } func (e *Err) Error() string { return fmt.Sprintf("unexpected requesting %q status code: %d", e.Response.Request.URL, e.Response.StatusCode, ) }