2016-05-16 16:57:37 +00:00
|
|
|
package httputil
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"regexp"
|
|
|
|
|
2016-11-15 17:01:18 +00:00
|
|
|
"github.com/git-lfs/git-lfs/auth"
|
|
|
|
"github.com/git-lfs/git-lfs/config"
|
|
|
|
"github.com/git-lfs/git-lfs/errors"
|
2016-05-16 16:57:37 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
lfsMediaTypeRE = regexp.MustCompile(`\Aapplication/vnd\.git\-lfs\+json(;|\z)`)
|
|
|
|
jsonMediaTypeRE = regexp.MustCompile(`\Aapplication/json(;|\z)`)
|
|
|
|
hiddenHeaders = map[string]bool{
|
|
|
|
"Authorization": true,
|
|
|
|
}
|
|
|
|
|
|
|
|
defaultErrors = map[int]string{
|
|
|
|
400: "Client error: %s",
|
|
|
|
401: "Authorization error: %s\nCheck that you have proper access to the repository",
|
|
|
|
403: "Authorization error: %s\nCheck that you have proper access to the repository",
|
|
|
|
404: "Repository or object not found: %s\nCheck that it exists and that you have proper access to it",
|
2016-08-22 16:37:04 +00:00
|
|
|
429: "Rate limit exceeded: %s",
|
2016-05-16 16:57:37 +00:00
|
|
|
500: "Server error: %s",
|
2016-08-22 16:37:04 +00:00
|
|
|
507: "Insufficient server storage: %s",
|
|
|
|
509: "Bandwidth limit exceeded: %s",
|
2016-05-16 16:57:37 +00:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// DecodeResponse attempts to decode the contents of the response as a JSON object
|
|
|
|
func DecodeResponse(res *http.Response, obj interface{}) error {
|
|
|
|
ctype := res.Header.Get("Content-Type")
|
|
|
|
if !(lfsMediaTypeRE.MatchString(ctype) || jsonMediaTypeRE.MatchString(ctype)) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
err := json.NewDecoder(res.Body).Decode(obj)
|
|
|
|
io.Copy(ioutil.Discard, res.Body)
|
|
|
|
res.Body.Close()
|
|
|
|
|
|
|
|
if err != nil {
|
2016-08-18 20:35:11 +00:00
|
|
|
return errors.Wrapf(err, "Unable to parse HTTP response for %s", TraceHttpReq(res.Request))
|
2016-05-16 16:57:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetDefaultError returns the default text for standard error codes (blank if none)
|
|
|
|
func GetDefaultError(code int) string {
|
|
|
|
if s, ok := defaultErrors[code]; ok {
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the response from a HTTP request for problems
|
2016-07-22 00:07:23 +00:00
|
|
|
func handleResponse(cfg *config.Configuration, res *http.Response, creds auth.Creds) error {
|
|
|
|
auth.SaveCredentials(cfg, creds, res)
|
2016-05-16 16:57:37 +00:00
|
|
|
|
|
|
|
if res.StatusCode < 400 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
io.Copy(ioutil.Discard, res.Body)
|
|
|
|
res.Body.Close()
|
|
|
|
}()
|
|
|
|
|
|
|
|
cliErr := &ClientError{}
|
|
|
|
err := DecodeResponse(res, cliErr)
|
|
|
|
if err == nil {
|
|
|
|
if len(cliErr.Message) == 0 {
|
|
|
|
err = defaultError(res)
|
|
|
|
} else {
|
2016-08-18 21:02:21 +00:00
|
|
|
err = errors.Wrap(cliErr, "http")
|
2016-05-16 16:57:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if res.StatusCode == 401 {
|
2016-08-18 21:24:11 +00:00
|
|
|
if err == nil {
|
|
|
|
err = errors.New("api: received status 401")
|
|
|
|
}
|
2016-08-18 20:20:33 +00:00
|
|
|
return errors.NewAuthError(err)
|
2016-05-16 16:57:37 +00:00
|
|
|
}
|
|
|
|
|
2016-08-22 16:37:04 +00:00
|
|
|
if res.StatusCode > 499 && res.StatusCode != 501 && res.StatusCode != 507 && res.StatusCode != 509 {
|
2016-08-18 21:24:11 +00:00
|
|
|
if err == nil {
|
|
|
|
err = errors.Errorf("api: received status %d", res.StatusCode)
|
|
|
|
}
|
2016-08-18 20:20:33 +00:00
|
|
|
return errors.NewFatalError(err)
|
2016-05-16 16:57:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func defaultError(res *http.Response) error {
|
|
|
|
var msgFmt string
|
|
|
|
|
|
|
|
if f, ok := defaultErrors[res.StatusCode]; ok {
|
|
|
|
msgFmt = f
|
|
|
|
} else if res.StatusCode < 500 {
|
|
|
|
msgFmt = defaultErrors[400] + fmt.Sprintf(" from HTTP %d", res.StatusCode)
|
|
|
|
} else {
|
|
|
|
msgFmt = defaultErrors[500] + fmt.Sprintf(" from HTTP %d", res.StatusCode)
|
|
|
|
}
|
|
|
|
|
2016-08-18 21:02:21 +00:00
|
|
|
return errors.Errorf(msgFmt, res.Request.URL)
|
2016-05-16 16:57:37 +00:00
|
|
|
}
|
|
|
|
|
2016-07-22 00:07:23 +00:00
|
|
|
func SetErrorResponseContext(cfg *config.Configuration, err error, res *http.Response) {
|
2016-08-19 20:03:39 +00:00
|
|
|
errors.SetContext(err, "Status", res.Status)
|
2016-05-16 16:57:37 +00:00
|
|
|
setErrorHeaderContext(err, "Request", res.Header)
|
2016-07-22 00:07:23 +00:00
|
|
|
setErrorRequestContext(cfg, err, res.Request)
|
2016-05-16 16:57:37 +00:00
|
|
|
}
|
|
|
|
|
2016-07-22 00:07:23 +00:00
|
|
|
func setErrorRequestContext(cfg *config.Configuration, err error, req *http.Request) {
|
2016-08-19 20:03:39 +00:00
|
|
|
errors.SetContext(err, "Endpoint", cfg.Endpoint(auth.GetOperationForRequest(req)).Url)
|
|
|
|
errors.SetContext(err, "URL", TraceHttpReq(req))
|
2016-05-16 16:57:37 +00:00
|
|
|
setErrorHeaderContext(err, "Response", req.Header)
|
|
|
|
}
|
|
|
|
|
|
|
|
func setErrorHeaderContext(err error, prefix string, head http.Header) {
|
|
|
|
for key, _ := range head {
|
|
|
|
contextKey := fmt.Sprintf("%s:%s", prefix, key)
|
|
|
|
if _, skip := hiddenHeaders[key]; skip {
|
2016-08-19 20:03:39 +00:00
|
|
|
errors.SetContext(err, contextKey, "--")
|
2016-05-16 16:57:37 +00:00
|
|
|
} else {
|
2016-08-19 20:03:39 +00:00
|
|
|
errors.SetContext(err, contextKey, head.Get(key))
|
2016-05-16 16:57:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|