git-lfs/lfs/client.go

814 lines
18 KiB
Go
Raw Normal View History

2015-03-19 19:30:55 +00:00
package lfs
2013-10-04 17:09:03 +00:00
import (
2015-03-20 01:55:40 +00:00
"bytes"
"encoding/base64"
2015-03-19 23:20:39 +00:00
"encoding/json"
"errors"
2013-10-04 17:09:03 +00:00
"fmt"
"io"
2015-03-19 23:20:39 +00:00
"io/ioutil"
2013-10-04 17:09:03 +00:00
"net/http"
"net/url"
2015-03-20 01:55:40 +00:00
"os"
"path/filepath"
2015-03-19 23:20:39 +00:00
"regexp"
2015-03-20 01:55:40 +00:00
"strconv"
"strings"
2015-05-13 19:43:41 +00:00
"github.com/github/git-lfs/git"
2015-05-25 18:20:50 +00:00
"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
2013-10-04 17:09:03 +00:00
)
const (
mediaType = "application/vnd.git-lfs+json; charset=utf-8"
)
2015-03-19 21:16:52 +00:00
var (
2015-08-23 00:40:06 +00:00
lfsMediaTypeRE = regexp.MustCompile(`\Aapplication/vnd\.git\-lfs\+json(;|\z)`)
jsonMediaTypeRE = regexp.MustCompile(`\Aapplication/json(;|\z)`)
hiddenHeaders = map[string]bool{
2015-03-19 21:16:52 +00:00
"Authorization": true,
}
2015-03-19 23:20:39 +00:00
defaultErrors = map[int]string{
400: "Client error: %s",
401: "Authorization error: %s\nCheck that you have proper access to the repository",
2015-03-27 15:53:02 +00:00
403: "Authorization error: %s\nCheck that you have proper access to the repository",
2015-03-19 23:20:39 +00:00
404: "Repository or object not found: %s\nCheck that it exists and that you have proper access to it",
500: "Server error: %s",
2015-09-01 14:48:12 +00:00
}
2015-03-19 21:16:52 +00:00
)
type ObjectError struct {
2015-08-07 13:32:28 +00:00
Code int `json:"code"`
Message string `json:"message"`
}
func (e *ObjectError) Error() string {
2015-08-07 13:32:28 +00:00
return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}
type ObjectResource struct {
2015-07-31 21:20:31 +00:00
Oid string `json:"oid,omitempty"`
Size int64 `json:"size"`
Actions map[string]*linkRelation `json:"actions,omitempty"`
Links map[string]*linkRelation `json:"_links,omitempty"`
Error *ObjectError `json:"error,omitempty"`
2015-02-26 01:09:53 +00:00
}
func (o *ObjectResource) NewRequest(relation, method string) (*http.Request, error) {
rel, ok := o.Rel(relation)
if !ok {
if relation == "download" {
return nil, errors.New("Object not found on the server.")
}
return nil, fmt.Errorf("No %q action for this object.", relation)
}
req, err := newClientRequest(method, rel.Href, rel.Header)
if err != nil {
return nil, err
}
return req, nil
}
func (o *ObjectResource) Rel(name string) (*linkRelation, bool) {
2015-08-07 13:37:03 +00:00
var rel *linkRelation
var ok bool
2015-08-07 13:37:03 +00:00
if o.Actions != nil {
rel, ok = o.Actions[name]
} else {
rel, ok = o.Links[name]
2015-02-26 01:09:53 +00:00
}
return rel, ok
}
type linkRelation struct {
Href string `json:"href"`
Header map[string]string `json:"header,omitempty"`
}
2015-03-19 21:16:52 +00:00
type ClientError struct {
Message string `json:"message"`
DocumentationUrl string `json:"documentation_url,omitempty"`
RequestId string `json:"request_id,omitempty"`
}
2015-03-19 21:16:52 +00:00
func (e *ClientError) Error() string {
msg := e.Message
if len(e.DocumentationUrl) > 0 {
msg += "\nDocs: " + e.DocumentationUrl
2013-10-04 17:09:03 +00:00
}
2015-03-19 21:16:52 +00:00
if len(e.RequestId) > 0 {
msg += "\nRequest ID: " + e.RequestId
}
2015-03-19 21:16:52 +00:00
return msg
2013-10-04 17:09:03 +00:00
}
// Download will attempt to download the object with the given oid. The batched
// API will be used, but if the server does not implement the batch operations
// it will fall back to the legacy API.
func Download(oid string, size int64) (io.ReadCloser, int64, error) {
if !Config.BatchTransfer() {
return DownloadLegacy(oid)
}
objects := []*ObjectResource{
&ObjectResource{Oid: oid, Size: size},
}
objs, err := Batch(objects, "download")
if err != nil {
if IsNotImplementedError(err) {
git.Config.SetLocal("", "lfs.batch", "false")
return DownloadLegacy(oid)
}
return nil, 0, err
}
if len(objs) != 1 { // Expecting to find one object
return nil, 0, Error(fmt.Errorf("Object not found: %s", oid))
}
return DownloadObject(objs[0])
}
// DownloadLegacy attempts to download the object for the given oid using the
// legacy API.
func DownloadLegacy(oid string) (io.ReadCloser, int64, error) {
req, err := newApiRequest("GET", oid)
2015-03-20 01:55:40 +00:00
if err != nil {
return nil, 0, Error(err)
}
2015-09-01 18:32:55 +00:00
res, obj, err := doLegacyApiRequest(req)
2015-08-21 18:31:06 +00:00
if err != nil {
return nil, 0, err
2015-03-20 01:55:40 +00:00
}
LogTransfer("lfs.api.download", res)
req, err = obj.NewRequest("download", "GET")
2015-03-20 01:55:40 +00:00
if err != nil {
return nil, 0, Error(err)
}
2015-09-01 18:32:55 +00:00
res, err = doStorageRequest(req)
2015-08-21 18:31:06 +00:00
if err != nil {
return nil, 0, err
2015-03-20 01:55:40 +00:00
}
LogTransfer("lfs.data.download", res)
2015-10-07 20:54:23 +00:00
2015-10-07 18:30:53 +00:00
return res.Body, res.ContentLength, nil
2015-03-19 21:16:52 +00:00
}
2015-01-23 22:11:10 +00:00
2015-03-26 18:46:33 +00:00
type byteCloser struct {
*bytes.Reader
}
func DownloadCheck(oid string) (*ObjectResource, error) {
req, err := newApiRequest("GET", oid)
2015-05-13 14:23:49 +00:00
if err != nil {
return nil, Error(err)
}
2015-09-01 18:32:55 +00:00
res, obj, err := doLegacyApiRequest(req)
2015-08-21 18:31:06 +00:00
if err != nil {
return nil, err
2015-05-13 14:23:49 +00:00
}
LogTransfer("lfs.api.download", res)
2015-05-13 14:23:49 +00:00
_, err = obj.NewRequest("download", "GET")
if err != nil {
return nil, Error(err)
}
2015-05-13 14:23:49 +00:00
return obj, nil
}
func DownloadObject(obj *ObjectResource) (io.ReadCloser, int64, error) {
req, err := obj.NewRequest("download", "GET")
2015-05-13 14:23:49 +00:00
if err != nil {
return nil, 0, Error(err)
}
2015-09-01 18:32:55 +00:00
res, err := doStorageRequest(req)
2015-08-21 18:31:06 +00:00
if err != nil {
return nil, 0, newRetriableError(err)
2015-05-13 14:23:49 +00:00
}
LogTransfer("lfs.data.download", res)
2015-10-07 20:54:23 +00:00
2015-10-07 18:30:53 +00:00
return res.Body, res.ContentLength, nil
2015-05-13 14:23:49 +00:00
}
2015-03-26 18:46:33 +00:00
func (b *byteCloser) Close() error {
return nil
}
func Batch(objects []*ObjectResource, operation string) ([]*ObjectResource, error) {
if len(objects) == 0 {
return nil, nil
}
2015-06-24 20:52:31 +00:00
o := map[string]interface{}{"objects": objects, "operation": operation}
by, err := json.Marshal(o)
2015-03-20 01:55:40 +00:00
if err != nil {
return nil, Error(err)
2015-03-20 01:55:40 +00:00
}
req, err := newBatchApiRequest(operation)
2015-03-20 01:55:40 +00:00
if err != nil {
return nil, Error(err)
}
req.Header.Set("Content-Type", mediaType)
req.Header.Set("Content-Length", strconv.Itoa(len(by)))
req.ContentLength = int64(len(by))
req.Body = &byteCloser{bytes.NewReader(by)}
tracerx.Printf("api: batch %d files", len(objects))
2015-09-01 18:32:55 +00:00
res, objs, err := doApiBatchRequest(req)
2015-10-07 20:54:23 +00:00
2015-08-21 18:31:06 +00:00
if err != nil {
2015-10-07 20:54:23 +00:00
if res == nil {
return nil, newRetriableError(err)
}
if res.StatusCode == 0 {
return nil, newRetriableError(err)
}
2015-10-07 20:54:23 +00:00
if IsAuthError(err) {
setAuthType(req, res)
2015-11-06 18:00:00 +00:00
return Batch(objects, operation)
}
switch res.StatusCode {
case 404, 410:
tracerx.Printf("api: batch not implemented: %d", res.StatusCode)
return nil, newNotImplementedError(nil)
}
2015-08-21 18:31:06 +00:00
tracerx.Printf("api error: %s", err)
2015-10-06 03:55:03 +00:00
return nil, Error(err)
}
LogTransfer("lfs.api.batch", res)
if res.StatusCode != 200 {
return nil, Error(fmt.Errorf("Invalid status for %s: %d", traceHttpReq(req), res.StatusCode))
}
return objs, nil
}
func UploadCheck(oidPath string) (*ObjectResource, error) {
oid := filepath.Base(oidPath)
stat, err := os.Stat(oidPath)
if err != nil {
return nil, Error(err)
2015-03-20 01:55:40 +00:00
}
reqObj := &ObjectResource{
2015-03-20 01:55:40 +00:00
Oid: oid,
Size: stat.Size(),
}
by, err := json.Marshal(reqObj)
if err != nil {
return nil, Error(err)
2015-03-20 01:55:40 +00:00
}
req, err := newApiRequest("POST", oid)
2015-03-20 01:55:40 +00:00
if err != nil {
return nil, Error(err)
2015-03-20 01:55:40 +00:00
}
req.Header.Set("Content-Type", mediaType)
req.Header.Set("Content-Length", strconv.Itoa(len(by)))
req.ContentLength = int64(len(by))
2015-03-26 18:46:33 +00:00
req.Body = &byteCloser{bytes.NewReader(by)}
2015-03-20 01:55:40 +00:00
tracerx.Printf("api: uploading (%s)", oid)
2015-09-01 18:32:55 +00:00
res, obj, err := doLegacyApiRequest(req)
2015-10-07 20:54:23 +00:00
2015-08-21 18:31:06 +00:00
if err != nil {
if IsAuthError(err) {
setAuthType(req, res)
2015-11-06 18:00:00 +00:00
return UploadCheck(oidPath)
}
return nil, newRetriableError(err)
2015-03-20 01:55:40 +00:00
}
LogTransfer("lfs.api.upload", res)
2015-03-20 01:55:40 +00:00
if res.StatusCode == 200 {
return nil, nil
}
2015-09-01 14:48:12 +00:00
if obj.Oid == "" {
obj.Oid = oid
}
if obj.Size == 0 {
obj.Size = reqObj.Size
}
return obj, nil
}
func UploadObject(o *ObjectResource, cb CopyCallback) error {
path, err := LocalMediaPath(o.Oid)
if err != nil {
return Error(err)
}
file, err := os.Open(path)
if err != nil {
return Error(err)
}
defer file.Close()
reader := &CallbackReader{
C: cb,
TotalSize: o.Size,
Reader: file,
}
req, err := o.NewRequest("upload", "PUT")
2015-03-20 01:55:40 +00:00
if err != nil {
return Error(err)
}
if len(req.Header.Get("Content-Type")) == 0 {
req.Header.Set("Content-Type", "application/octet-stream")
}
2015-09-01 18:32:55 +00:00
if req.Header.Get("Transfer-Encoding") == "chunked" {
req.TransferEncoding = []string{"chunked"}
} else {
req.Header.Set("Content-Length", strconv.FormatInt(o.Size, 10))
}
2015-03-20 18:01:43 +00:00
2015-09-01 18:32:55 +00:00
req.ContentLength = o.Size
req.Body = ioutil.NopCloser(reader)
2015-03-20 01:55:40 +00:00
2015-09-01 18:32:55 +00:00
res, err := doStorageRequest(req)
2015-08-21 18:31:06 +00:00
if err != nil {
return newRetriableError(err)
2015-03-20 01:55:40 +00:00
}
LogTransfer("lfs.data.upload", res)
2015-03-20 01:55:40 +00:00
// A status code of 403 likely means that an authentication token for the
// upload has expired. This can be safely retried.
if res.StatusCode == 403 {
return newRetriableError(err)
}
2015-03-20 01:55:40 +00:00
if res.StatusCode > 299 {
return Errorf(nil, "Invalid status for %s: %d", traceHttpReq(req), res.StatusCode)
2015-03-20 01:55:40 +00:00
}
io.Copy(ioutil.Discard, res.Body)
res.Body.Close()
2015-08-23 00:40:06 +00:00
if _, ok := o.Rel("verify"); !ok {
2015-03-20 01:55:40 +00:00
return nil
2015-08-23 00:40:06 +00:00
}
2015-09-01 18:32:55 +00:00
req, err = o.NewRequest("verify", "POST")
2015-08-23 00:40:06 +00:00
if err != nil {
2015-03-20 01:55:40 +00:00
return Error(err)
}
by, err := json.Marshal(o)
if err != nil {
return Error(err)
}
2015-03-20 01:55:40 +00:00
req.Header.Set("Content-Type", mediaType)
req.Header.Set("Content-Length", strconv.Itoa(len(by)))
req.ContentLength = int64(len(by))
2015-03-20 01:55:40 +00:00
req.Body = ioutil.NopCloser(bytes.NewReader(by))
res, err = doAPIRequest(req, true)
2015-08-21 18:31:06 +00:00
if err != nil {
return err
}
LogTransfer("lfs.data.verify", res)
io.Copy(ioutil.Discard, res.Body)
res.Body.Close()
2015-03-20 01:55:40 +00:00
2015-08-21 18:31:06 +00:00
return err
2015-01-23 22:11:10 +00:00
}
// doLegacyApiRequest runs the request to the LFS legacy API.
func doLegacyApiRequest(req *http.Request) (*http.Response, *ObjectResource, error) {
via := make([]*http.Request, 0, 4)
2015-09-01 18:32:55 +00:00
res, err := doApiRequestWithRedirects(req, via, true)
if err != nil {
return res, nil, err
}
obj := &ObjectResource{}
2015-09-01 18:32:55 +00:00
err = decodeApiResponse(res, obj)
2015-09-01 18:32:55 +00:00
if err != nil {
setErrorResponseContext(err, res)
return nil, nil, err
}
return res, obj, nil
}
2016-01-25 17:03:57 +00:00
// getOperationForHttpRequest determines the operation type for a http.Request
func getOperationForHttpRequest(req *http.Request) string {
operation := "download"
if req.Method == "POST" || req.Method == "PUT" {
operation = "upload"
}
return operation
}
// doApiBatchRequest runs the request to the LFS batch API. If the API returns a
// 401, the repo will be marked as having private access and the request will be
// re-run. When the repo is marked as having private access, credentials will
// be retrieved.
func doApiBatchRequest(req *http.Request) (*http.Response, []*ObjectResource, error) {
res, err := doAPIRequest(req, Config.PrivateAccess(getOperationForHttpRequest(req)))
2015-09-01 18:32:55 +00:00
if err != nil {
2015-10-23 15:59:28 +00:00
if res != nil && res.StatusCode == 401 {
return res, nil, newAuthError(err)
}
2015-09-01 18:32:55 +00:00
return res, nil, err
}
var objs map[string][]*ObjectResource
2015-09-01 18:32:55 +00:00
err = decodeApiResponse(res, &objs)
2015-09-01 18:32:55 +00:00
if err != nil {
setErrorResponseContext(err, res)
}
2015-09-01 18:32:55 +00:00
return res, objs["objects"], err
}
// doStorageREquest runs the request to the storage API from a link provided by
// the "actions" or "_links" properties an LFS API response.
2015-09-01 18:32:55 +00:00
func doStorageRequest(req *http.Request) (*http.Response, error) {
creds, err := getCreds(req)
if err != nil {
return nil, err
}
return doHttpRequest(req, creds)
}
// doAPIRequest runs the request to the LFS API, without parsing the response
// body. If the API returns a 401, the repo will be marked as having private
// access and the request will be re-run. When the repo is marked as having
// private access, credentials will be retrieved.
func doAPIRequest(req *http.Request, useCreds bool) (*http.Response, error) {
via := make([]*http.Request, 0, 4)
return doApiRequestWithRedirects(req, via, useCreds)
}
// doHttpRequest runs the given HTTP request. LFS or Storage API requests should
// use doApiBatchRequest() or doStorageRequest() instead.
func doHttpRequest(req *http.Request, creds Creds) (*http.Response, error) {
2015-10-07 20:54:23 +00:00
var (
res *http.Response
err error
)
2015-10-07 20:54:23 +00:00
if Config.NtlmAccess(getOperationForHttpRequest(req)) {
2015-10-07 20:54:23 +00:00
res, err = DoNTLMRequest(req, true)
} else {
res, err = Config.HttpClient(req.Host).Do(req)
}
2015-10-07 20:54:23 +00:00
2015-07-21 20:44:40 +00:00
if res == nil {
res = &http.Response{
StatusCode: 0,
Header: make(http.Header),
Request: req,
Body: ioutil.NopCloser(bytes.NewBufferString("")),
}
}
2015-03-19 23:20:39 +00:00
if err != nil {
2015-11-06 18:00:00 +00:00
if IsAuthError(err) {
setAuthType(req, res)
doHttpRequest(req, creds)
} else {
err = Error(err)
}
2015-03-19 23:20:39 +00:00
} else {
2015-09-01 18:32:55 +00:00
err = handleResponse(res, creds)
2015-03-19 23:20:39 +00:00
}
2015-08-21 18:31:06 +00:00
if err != nil {
2015-03-19 23:20:39 +00:00
if res != nil {
2015-08-21 18:31:06 +00:00
setErrorResponseContext(err, res)
2015-03-19 23:20:39 +00:00
} else {
2015-08-21 18:31:06 +00:00
setErrorRequestContext(err, req)
2015-03-19 23:20:39 +00:00
}
}
2015-08-21 18:31:06 +00:00
return res, err
2015-03-19 23:20:39 +00:00
}
2015-09-01 18:32:55 +00:00
func doApiRequestWithRedirects(req *http.Request, via []*http.Request, useCreds bool) (*http.Response, error) {
var creds Creds
if useCreds {
2016-04-08 20:53:02 +00:00
c, err := getCreds(req)
if err != nil {
return nil, err
}
creds = c
}
2015-08-21 18:31:06 +00:00
res, err := doHttpRequest(req, creds)
if err != nil {
return res, err
2015-03-26 18:46:33 +00:00
}
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 := newClientRequest(req.Method, redirectTo, nil)
2015-03-26 18:46:33 +00:00
if err != nil {
return res, Errorf(err, err.Error())
2015-03-26 18:46:33 +00:00
}
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)
2015-05-11 14:42:31 +00:00
if !ok {
return res, Errorf(nil, "Request body needs to be an io.Seeker to handle redirects.")
2015-03-26 18:46:33 +00:00
}
2015-05-11 14:42:31 +00:00
if _, err := seeker.Seek(0, 0); err != nil {
return res, Error(err)
2015-05-11 14:42:31 +00:00
}
redirectedReq.Body = realBody
2015-05-11 14:42:31 +00:00
redirectedReq.ContentLength = req.ContentLength
2015-03-26 18:46:33 +00:00
if err = checkRedirect(redirectedReq, via); err != nil {
return res, Errorf(err, err.Error())
2015-03-26 18:46:33 +00:00
}
return doApiRequestWithRedirects(redirectedReq, via, useCreds)
2015-03-26 18:46:33 +00:00
}
2015-05-11 14:42:23 +00:00
return res, nil
2015-03-26 18:46:33 +00:00
}
2015-09-01 18:32:55 +00:00
func handleResponse(res *http.Response, creds Creds) error {
saveCredentials(creds, res)
2015-03-19 23:20:39 +00:00
if res.StatusCode < 400 {
return nil
}
defer func() {
io.Copy(ioutil.Discard, res.Body)
res.Body.Close()
}()
2015-03-20 01:55:40 +00:00
cliErr := &ClientError{}
2015-08-21 18:31:06 +00:00
err := decodeApiResponse(res, cliErr)
if err == nil {
2015-03-20 01:55:40 +00:00
if len(cliErr.Message) == 0 {
2015-08-21 18:31:06 +00:00
err = defaultError(res)
2015-03-20 01:55:40 +00:00
} else {
2015-08-21 18:31:06 +00:00
err = Error(cliErr)
2015-03-19 23:20:39 +00:00
}
}
if res.StatusCode == 401 {
return newAuthError(err)
}
if res.StatusCode > 499 && res.StatusCode != 501 && res.StatusCode != 509 {
2015-08-21 18:31:06 +00:00
return newFatalError(err)
}
2015-08-21 18:31:06 +00:00
return err
2015-03-19 23:20:39 +00:00
}
func decodeApiResponse(res *http.Response, obj interface{}) error {
2015-03-20 17:10:38 +00:00
ctype := res.Header.Get("Content-Type")
2015-03-22 18:32:22 +00:00
if !(lfsMediaTypeRE.MatchString(ctype) || jsonMediaTypeRE.MatchString(ctype)) {
2015-03-20 01:55:40 +00:00
return nil
}
2015-09-01 14:49:43 +00:00
err := json.NewDecoder(res.Body).Decode(obj)
io.Copy(ioutil.Discard, res.Body)
res.Body.Close()
2015-03-20 01:55:40 +00:00
if err != nil {
return Errorf(err, "Unable to parse HTTP response for %s", traceHttpReq(res.Request))
2015-03-20 01:55:40 +00:00
}
return nil
}
func defaultError(res *http.Response) error {
2015-03-19 23:20:39 +00:00
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)
}
return Error(fmt.Errorf(msgFmt, res.Request.URL))
}
func newApiRequest(method, oid string) (*http.Request, error) {
objectOid := oid
operation := "download"
if method == "POST" {
if oid != "batch" {
objectOid = ""
operation = "upload"
}
}
endpoint := Config.Endpoint(operation)
res, err := sshAuthenticate(endpoint, operation, oid)
if err != nil {
tracerx.Printf("ssh: attempted with %s. Error: %s",
endpoint.SshUserAndHost, err.Error(),
)
return nil, err
}
if len(res.Href) > 0 {
endpoint.Url = res.Href
}
u, err := ObjectUrl(endpoint, objectOid)
2015-01-23 22:11:10 +00:00
if err != nil {
return nil, err
2015-01-23 22:11:10 +00:00
}
req, err := newClientRequest(method, u.String(), res.Header)
2015-05-11 14:42:34 +00:00
if err != nil {
return nil, err
2015-05-11 14:42:34 +00:00
}
req.Header.Set("Accept", mediaType)
return req, nil
2015-01-23 22:11:10 +00:00
}
func newClientRequest(method, rawurl string, header map[string]string) (*http.Request, error) {
2015-03-19 21:16:52 +00:00
req, err := http.NewRequest(method, rawurl, nil)
if err != nil {
return nil, err
}
for key, value := range header {
req.Header.Set(key, value)
}
2015-03-19 21:16:52 +00:00
req.Header.Set("User-Agent", UserAgent)
return req, nil
}
func newBatchApiRequest(operation string) (*http.Request, error) {
endpoint := Config.Endpoint(operation)
2015-06-18 16:31:33 +00:00
res, err := sshAuthenticate(endpoint, operation, "")
2015-06-18 16:31:33 +00:00
if err != nil {
tracerx.Printf("ssh: %s attempted with %s. Error: %s",
operation, endpoint.SshUserAndHost, err.Error(),
2015-06-18 16:31:33 +00:00
)
return nil, err
2015-06-18 16:31:33 +00:00
}
if len(res.Href) > 0 {
endpoint.Url = res.Href
}
u, err := ObjectUrl(endpoint, "batch")
if err != nil {
return nil, err
2015-06-18 16:31:33 +00:00
}
req, err := newBatchClientRequest("POST", u.String())
2015-06-18 16:31:33 +00:00
if err != nil {
return nil, err
2015-06-18 16:31:33 +00:00
}
req.Header.Set("Accept", mediaType)
if res.Header != nil {
for key, value := range res.Header {
req.Header.Set(key, value)
}
}
return req, nil
2015-06-18 16:31:33 +00:00
}
func newBatchClientRequest(method, rawurl string) (*http.Request, error) {
2015-06-18 16:31:33 +00:00
req, err := http.NewRequest(method, rawurl, nil)
2015-03-19 21:16:52 +00:00
if err != nil {
return nil, err
2015-02-17 18:46:08 +00:00
}
2015-06-18 16:31:33 +00:00
req.Header.Set("User-Agent", UserAgent)
2015-02-17 18:46:08 +00:00
return req, nil
}
func setRequestAuthFromUrl(req *http.Request, u *url.URL) bool {
if !Config.NtlmAccess(getOperationForHttpRequest(req)) && u.User != nil {
if pass, ok := u.User.Password(); ok {
fmt.Fprintln(os.Stderr, "warning: current Git remote contains credentials")
setRequestAuth(req, u.User.Username(), pass)
return true
}
}
return false
}
func setAuthType(req *http.Request, res *http.Response) {
2015-11-06 18:00:00 +00:00
authType := getAuthType(res)
operation := getOperationForHttpRequest(req)
Config.SetAccess(operation, authType)
tracerx.Printf("api: http response indicates %q authentication. Resubmitting...", authType)
2015-10-07 18:30:53 +00:00
}
2015-11-06 18:00:00 +00:00
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"
}
func setRequestAuth(req *http.Request, user, pass string) {
if Config.NtlmAccess(getOperationForHttpRequest(req)) {
2015-10-07 18:30:53 +00:00
return
2015-08-28 20:40:41 +00:00
}
2015-10-07 20:54:23 +00:00
2015-10-07 18:30:53 +00:00
if len(user) == 0 && len(pass) == 0 {
return
}
token := fmt.Sprintf("%s:%s", user, pass)
auth := "Basic " + strings.TrimSpace(base64.StdEncoding.EncodeToString([]byte(token)))
2015-10-07 18:30:53 +00:00
req.Header.Set("Authorization", auth)
2014-08-08 20:02:44 +00:00
}
func setErrorResponseContext(err error, res *http.Response) {
ErrorSetContext(err, "Status", res.Status)
2014-08-08 20:02:44 +00:00
setErrorHeaderContext(err, "Request", res.Header)
2014-08-08 17:31:33 +00:00
setErrorRequestContext(err, res.Request)
2013-11-02 00:23:37 +00:00
}
func setErrorRequestContext(err error, req *http.Request) {
ErrorSetContext(err, "Endpoint", Config.Endpoint(getOperationForHttpRequest(req)).Url)
ErrorSetContext(err, "URL", traceHttpReq(req))
setErrorHeaderContext(err, "Response", req.Header)
}
func setErrorHeaderContext(err error, prefix string, head http.Header) {
2014-08-08 20:02:44 +00:00
for key, _ := range head {
contextKey := fmt.Sprintf("%s:%s", prefix, key)
if _, skip := hiddenHeaders[key]; skip {
ErrorSetContext(err, contextKey, "--")
2014-08-08 20:02:44 +00:00
} else {
ErrorSetContext(err, contextKey, head.Get(key))
2014-08-08 20:02:44 +00:00
}
}
2015-10-07 20:54:23 +00:00
}