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  }