github.com/ohlinux/git-lfs@v1.5.4/httputil/request.go (about) 1 package httputil 2 3 import ( 4 "bytes" 5 "io" 6 "io/ioutil" 7 "net/http" 8 "net/url" 9 "strings" 10 11 "github.com/git-lfs/git-lfs/auth" 12 "github.com/git-lfs/git-lfs/config" 13 "github.com/git-lfs/git-lfs/errors" 14 15 "github.com/rubyist/tracerx" 16 ) 17 18 type ClientError struct { 19 Message string `json:"message"` 20 DocumentationUrl string `json:"documentation_url,omitempty"` 21 RequestId string `json:"request_id,omitempty"` 22 } 23 24 const ( 25 basicAuthType = "basic" 26 ntlmAuthType = "ntlm" 27 negotiateAuthType = "negotiate" 28 ) 29 30 var ( 31 authenticateHeaders = []string{"Lfs-Authenticate", "Www-Authenticate"} 32 ) 33 34 func (e *ClientError) Error() string { 35 msg := e.Message 36 if len(e.DocumentationUrl) > 0 { 37 msg += "\nDocs: " + e.DocumentationUrl 38 } 39 if len(e.RequestId) > 0 { 40 msg += "\nRequest ID: " + e.RequestId 41 } 42 return msg 43 } 44 45 // Internal http request management 46 func doHttpRequest(cfg *config.Configuration, req *http.Request, creds auth.Creds) (*http.Response, error) { 47 var ( 48 res *http.Response 49 cause string 50 err error 51 ) 52 53 if cfg.NtlmAccess(auth.GetOperationForRequest(req)) { 54 cause = "ntlm" 55 res, err = doNTLMRequest(cfg, req, true) 56 } else { 57 cause = "http" 58 res, err = NewHttpClient(cfg, req.Host).Do(req) 59 } 60 61 if res == nil { 62 res = &http.Response{ 63 StatusCode: 0, 64 Header: make(http.Header), 65 Request: req, 66 Body: ioutil.NopCloser(bytes.NewBufferString("")), 67 } 68 } 69 70 if err != nil { 71 if errors.IsAuthError(err) { 72 SetAuthType(cfg, req, res) 73 doHttpRequest(cfg, req, creds) 74 } else { 75 err = errors.Wrap(err, cause) 76 } 77 } else { 78 err = handleResponse(cfg, res, creds) 79 } 80 81 if err != nil { 82 if res != nil { 83 SetErrorResponseContext(cfg, err, res) 84 } else { 85 setErrorRequestContext(cfg, err, req) 86 } 87 } 88 89 return res, err 90 } 91 92 // DoHttpRequest performs a single HTTP request 93 func DoHttpRequest(cfg *config.Configuration, req *http.Request, useCreds bool) (*http.Response, error) { 94 var creds auth.Creds 95 if useCreds { 96 c, err := auth.GetCreds(cfg, req) 97 if err != nil { 98 return nil, err 99 } 100 creds = c 101 } 102 103 return doHttpRequest(cfg, req, creds) 104 } 105 106 // DoHttpRequestWithRedirects runs a HTTP request and responds to redirects 107 func DoHttpRequestWithRedirects(cfg *config.Configuration, req *http.Request, via []*http.Request, useCreds bool) (*http.Response, error) { 108 var creds auth.Creds 109 if useCreds { 110 c, err := auth.GetCreds(cfg, req) 111 if err != nil { 112 return nil, err 113 } 114 creds = c 115 } 116 117 res, err := doHttpRequest(cfg, req, creds) 118 if err != nil { 119 return res, err 120 } 121 122 if res.StatusCode == 307 { 123 redirectTo := res.Header.Get("Location") 124 locurl, err := url.Parse(redirectTo) 125 if err == nil && !locurl.IsAbs() { 126 locurl = req.URL.ResolveReference(locurl) 127 redirectTo = locurl.String() 128 } 129 130 redirectedReq, err := NewHttpRequest(req.Method, redirectTo, nil) 131 if err != nil { 132 return res, errors.Wrapf(err, err.Error()) 133 } 134 135 via = append(via, req) 136 137 // Avoid seeking and re-wrapping the CountingReadCloser, just get the "real" body 138 realBody := req.Body 139 if wrappedBody, ok := req.Body.(*CountingReadCloser); ok { 140 realBody = wrappedBody.ReadCloser 141 } 142 143 seeker, ok := realBody.(io.Seeker) 144 if !ok { 145 return res, errors.Wrapf(nil, "Request body needs to be an io.Seeker to handle redirects.") 146 } 147 148 if _, err := seeker.Seek(0, 0); err != nil { 149 return res, errors.Wrap(err, "request retry") 150 } 151 redirectedReq.Body = realBody 152 redirectedReq.ContentLength = req.ContentLength 153 154 if err = CheckRedirect(redirectedReq, via); err != nil { 155 return res, errors.Wrapf(err, err.Error()) 156 } 157 158 return DoHttpRequestWithRedirects(cfg, redirectedReq, via, useCreds) 159 } 160 161 return res, nil 162 } 163 164 // NewHttpRequest creates a template request, with the given headers & UserAgent supplied 165 func NewHttpRequest(method, rawurl string, header map[string]string) (*http.Request, error) { 166 req, err := http.NewRequest(method, rawurl, nil) 167 if err != nil { 168 return nil, err 169 } 170 171 for key, value := range header { 172 req.Header.Set(key, value) 173 } 174 175 req.Header.Set("User-Agent", UserAgent) 176 177 return req, nil 178 } 179 180 func SetAuthType(cfg *config.Configuration, req *http.Request, res *http.Response) { 181 authType := GetAuthType(res) 182 operation := auth.GetOperationForRequest(req) 183 cfg.SetAccess(operation, authType) 184 tracerx.Printf("api: http response indicates %q authentication. Resubmitting...", authType) 185 } 186 187 func GetAuthType(res *http.Response) string { 188 for _, headerName := range authenticateHeaders { 189 for _, auth := range res.Header[headerName] { 190 191 authLower := strings.ToLower(auth) 192 // When server sends Www-Authentication: Negotiate, it supports both Kerberos and NTLM. 193 // Since git-lfs current does not support Kerberos, we will return NTLM in this case. 194 if strings.HasPrefix(authLower, ntlmAuthType) || strings.HasPrefix(authLower, negotiateAuthType) { 195 return ntlmAuthType 196 } 197 } 198 } 199 200 return basicAuthType 201 }