4593d0a641
- script/vendor received an update in order to work with Glide - import paths have been rewritten to work with GO15VENDOREXPERIMENT
187 lines
4.2 KiB
Go
187 lines
4.2 KiB
Go
package httputil
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/github/git-lfs/auth"
|
|
"github.com/github/git-lfs/config"
|
|
"github.com/github/git-lfs/errutil"
|
|
|
|
"github.com/rubyist/tracerx"
|
|
)
|
|
|
|
type ClientError struct {
|
|
Message string `json:"message"`
|
|
DocumentationUrl string `json:"documentation_url,omitempty"`
|
|
RequestId string `json:"request_id,omitempty"`
|
|
}
|
|
|
|
func (e *ClientError) Error() string {
|
|
msg := e.Message
|
|
if len(e.DocumentationUrl) > 0 {
|
|
msg += "\nDocs: " + e.DocumentationUrl
|
|
}
|
|
if len(e.RequestId) > 0 {
|
|
msg += "\nRequest ID: " + e.RequestId
|
|
}
|
|
return msg
|
|
}
|
|
|
|
// Internal http request management
|
|
func doHttpRequest(req *http.Request, creds auth.Creds) (*http.Response, error) {
|
|
var (
|
|
res *http.Response
|
|
err error
|
|
)
|
|
|
|
if config.Config.NtlmAccess(auth.GetOperationForRequest(req)) {
|
|
res, err = doNTLMRequest(req, true)
|
|
} else {
|
|
res, err = NewHttpClient(config.Config, req.Host).Do(req)
|
|
}
|
|
|
|
if res == nil {
|
|
res = &http.Response{
|
|
StatusCode: 0,
|
|
Header: make(http.Header),
|
|
Request: req,
|
|
Body: ioutil.NopCloser(bytes.NewBufferString("")),
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
if errutil.IsAuthError(err) {
|
|
SetAuthType(req, res)
|
|
doHttpRequest(req, creds)
|
|
} else {
|
|
err = errutil.Error(err)
|
|
}
|
|
} else {
|
|
// TODO(sinbad) stop handling the response here, separate response processing to api package
|
|
err = handleResponse(res, creds)
|
|
}
|
|
|
|
if err != nil {
|
|
if res != nil {
|
|
SetErrorResponseContext(err, res)
|
|
} else {
|
|
setErrorRequestContext(err, req)
|
|
}
|
|
}
|
|
|
|
return res, err
|
|
}
|
|
|
|
// DoHttpRequest performs a single HTTP request
|
|
func DoHttpRequest(req *http.Request, useCreds bool) (*http.Response, error) {
|
|
var creds auth.Creds
|
|
if useCreds {
|
|
c, err := auth.GetCreds(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
creds = c
|
|
}
|
|
|
|
return doHttpRequest(req, creds)
|
|
}
|
|
|
|
// DoHttpRequestWithRedirects runs a HTTP request and responds to redirects
|
|
func DoHttpRequestWithRedirects(req *http.Request, via []*http.Request, useCreds bool) (*http.Response, error) {
|
|
var creds auth.Creds
|
|
if useCreds {
|
|
c, err := auth.GetCreds(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
creds = c
|
|
}
|
|
|
|
res, err := doHttpRequest(req, creds)
|
|
if err != nil {
|
|
return res, err
|
|
}
|
|
|
|
if res.StatusCode == 307 {
|
|
redirectTo := res.Header.Get("Location")
|
|
locurl, err := url.Parse(redirectTo)
|
|
if err == nil && !locurl.IsAbs() {
|
|
locurl = req.URL.ResolveReference(locurl)
|
|
redirectTo = locurl.String()
|
|
}
|
|
|
|
redirectedReq, err := NewHttpRequest(req.Method, redirectTo, nil)
|
|
if err != nil {
|
|
return res, errutil.Errorf(err, err.Error())
|
|
}
|
|
|
|
via = append(via, req)
|
|
|
|
// Avoid seeking and re-wrapping the CountingReadCloser, just get the "real" body
|
|
realBody := req.Body
|
|
if wrappedBody, ok := req.Body.(*CountingReadCloser); ok {
|
|
realBody = wrappedBody.ReadCloser
|
|
}
|
|
|
|
seeker, ok := realBody.(io.Seeker)
|
|
if !ok {
|
|
return res, errutil.Errorf(nil, "Request body needs to be an io.Seeker to handle redirects.")
|
|
}
|
|
|
|
if _, err := seeker.Seek(0, 0); err != nil {
|
|
return res, errutil.Error(err)
|
|
}
|
|
redirectedReq.Body = realBody
|
|
redirectedReq.ContentLength = req.ContentLength
|
|
|
|
if err = CheckRedirect(redirectedReq, via); err != nil {
|
|
return res, errutil.Errorf(err, err.Error())
|
|
}
|
|
|
|
return DoHttpRequestWithRedirects(redirectedReq, via, useCreds)
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
// NewHttpRequest creates a template request, with the given headers & UserAgent supplied
|
|
func NewHttpRequest(method, rawurl string, header map[string]string) (*http.Request, error) {
|
|
req, err := http.NewRequest(method, rawurl, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for key, value := range header {
|
|
req.Header.Set(key, value)
|
|
}
|
|
|
|
req.Header.Set("User-Agent", UserAgent)
|
|
|
|
return req, nil
|
|
}
|
|
|
|
func SetAuthType(req *http.Request, res *http.Response) {
|
|
authType := GetAuthType(res)
|
|
operation := auth.GetOperationForRequest(req)
|
|
config.Config.SetAccess(operation, authType)
|
|
tracerx.Printf("api: http response indicates %q authentication. Resubmitting...", authType)
|
|
}
|
|
|
|
func GetAuthType(res *http.Response) string {
|
|
auth := res.Header.Get("Www-Authenticate")
|
|
if len(auth) < 1 {
|
|
auth = res.Header.Get("Lfs-Authenticate")
|
|
}
|
|
|
|
if strings.HasPrefix(strings.ToLower(auth), "ntlm") {
|
|
return "ntlm"
|
|
}
|
|
|
|
return "basic"
|
|
}
|