2015-02-12 23:18:47 +00:00
|
|
|
package hawser
|
2013-10-04 17:09:03 +00:00
|
|
|
|
|
|
|
import (
|
2015-01-23 22:11:10 +00:00
|
|
|
"bytes"
|
2013-10-31 22:12:30 +00:00
|
|
|
"encoding/base64"
|
2013-10-31 19:00:09 +00:00
|
|
|
"encoding/json"
|
2014-03-27 14:59:32 +00:00
|
|
|
"errors"
|
2013-10-04 17:09:03 +00:00
|
|
|
"fmt"
|
2014-03-12 15:31:41 +00:00
|
|
|
"github.com/cheggaaa/pb"
|
2015-01-23 22:11:10 +00:00
|
|
|
"github.com/rubyist/tracerx"
|
2013-10-22 18:21:01 +00:00
|
|
|
"io"
|
2014-03-12 15:31:41 +00:00
|
|
|
"io/ioutil"
|
2014-04-16 14:30:13 +00:00
|
|
|
"mime"
|
2013-10-04 17:09:03 +00:00
|
|
|
"net/http"
|
|
|
|
"os"
|
2014-08-08 17:52:58 +00:00
|
|
|
"path/filepath"
|
2015-02-26 01:32:33 +00:00
|
|
|
"strings"
|
2013-10-04 17:09:03 +00:00
|
|
|
)
|
|
|
|
|
2013-12-09 15:34:29 +00:00
|
|
|
const (
|
2015-02-26 01:05:11 +00:00
|
|
|
// Legacy type
|
|
|
|
gitMediaType = "application/vnd.git-media"
|
|
|
|
|
2015-02-26 01:32:33 +00:00
|
|
|
// The main type, sub type, and suffix. Use this when ensuring the type from
|
|
|
|
// an HTTP response is correct.
|
|
|
|
gitMediaMetaTypePrefix = gitMediaType + "+json"
|
|
|
|
|
2015-02-26 01:05:11 +00:00
|
|
|
// Adds the extra mime params. Use this when sending the type in an HTTP
|
|
|
|
// request.
|
2015-02-26 01:32:33 +00:00
|
|
|
gitMediaMetaType = gitMediaMetaTypePrefix + "; charset=utf-8"
|
2013-12-09 15:34:29 +00:00
|
|
|
)
|
|
|
|
|
2015-02-13 19:01:25 +00:00
|
|
|
func Download(oidPath string) (io.ReadCloser, int64, *WrappedError) {
|
|
|
|
oid := filepath.Base(oidPath)
|
2015-02-17 19:00:19 +00:00
|
|
|
req, creds, err := request("GET", oid)
|
2015-02-12 23:22:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, 0, Error(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
req.Header.Set("Accept", gitMediaType)
|
|
|
|
res, wErr := doRequest(req, creds)
|
|
|
|
|
|
|
|
if wErr != nil {
|
|
|
|
return nil, 0, wErr
|
|
|
|
}
|
|
|
|
|
|
|
|
contentType := res.Header.Get("Content-Type")
|
|
|
|
if contentType == "" {
|
|
|
|
wErr = Error(errors.New("Empty Content-Type"))
|
|
|
|
setErrorResponseContext(wErr, res)
|
|
|
|
return nil, 0, wErr
|
|
|
|
}
|
|
|
|
|
2015-02-26 01:32:33 +00:00
|
|
|
if strings.HasPrefix(contentType, gitMediaMetaTypePrefix) {
|
|
|
|
obj := &objectResource{}
|
|
|
|
err := json.NewDecoder(res.Body).Decode(obj)
|
|
|
|
res.Body.Close()
|
|
|
|
if err != nil {
|
|
|
|
wErr := Error(err)
|
|
|
|
setErrorResponseContext(wErr, res)
|
|
|
|
return nil, 0, wErr
|
|
|
|
}
|
|
|
|
|
|
|
|
dlReq, err := obj.NewRequest("download", "GET")
|
|
|
|
if err != nil {
|
|
|
|
wErr := Error(err)
|
|
|
|
setErrorResponseContext(wErr, res)
|
|
|
|
return nil, 0, wErr
|
|
|
|
}
|
|
|
|
|
|
|
|
dlCreds, err := setRequestHeaders(dlReq)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, Errorf(err, "Error attempting to GET %s", oidPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
dlRes, err := DoHTTP(Config, dlReq)
|
|
|
|
if err != nil {
|
|
|
|
wErr := Error(err)
|
|
|
|
setErrorResponseContext(wErr, res)
|
|
|
|
return nil, 0, wErr
|
|
|
|
}
|
|
|
|
|
|
|
|
saveCredentials(dlCreds, dlRes)
|
|
|
|
|
|
|
|
contentType := dlRes.Header.Get("Content-Type")
|
|
|
|
if contentType == "" {
|
|
|
|
wErr = Error(errors.New("Empty Content-Type"))
|
|
|
|
setErrorResponseContext(wErr, res)
|
|
|
|
return nil, 0, wErr
|
|
|
|
}
|
|
|
|
|
|
|
|
res = dlRes
|
|
|
|
}
|
|
|
|
|
2015-02-13 23:30:47 +00:00
|
|
|
ok, headerSize, wErr := validateMediaHeader(contentType, res.Body)
|
|
|
|
if !ok {
|
2015-02-12 23:22:54 +00:00
|
|
|
setErrorResponseContext(wErr, res)
|
|
|
|
return nil, 0, wErr
|
|
|
|
}
|
|
|
|
|
2015-02-13 23:30:47 +00:00
|
|
|
return res.Body, res.ContentLength - int64(headerSize), nil
|
2015-02-12 23:22:54 +00:00
|
|
|
}
|
|
|
|
|
2015-02-13 19:01:25 +00:00
|
|
|
func Upload(oidPath, filename string, cb CopyCallback) *WrappedError {
|
|
|
|
linkMeta, status, err := callPost(oidPath, filename)
|
2015-02-12 02:28:42 +00:00
|
|
|
if err != nil && status != 302 {
|
2015-02-12 23:18:47 +00:00
|
|
|
return Errorf(err, "Error starting file upload.")
|
2015-02-12 02:28:42 +00:00
|
|
|
}
|
|
|
|
|
2015-02-13 19:01:25 +00:00
|
|
|
oid := filepath.Base(oidPath)
|
2015-02-12 02:28:42 +00:00
|
|
|
|
|
|
|
switch status {
|
2015-02-13 20:37:19 +00:00
|
|
|
case 200: // object exists on the server
|
2015-02-12 02:28:42 +00:00
|
|
|
case 405, 302:
|
|
|
|
// Do the old style OPTIONS + PUT
|
2015-02-17 18:46:08 +00:00
|
|
|
status, wErr := callOptions(oidPath)
|
|
|
|
if wErr != nil {
|
|
|
|
return wErr
|
2015-02-12 02:28:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if status != 200 {
|
2015-02-13 19:01:25 +00:00
|
|
|
err = callPut(oidPath, filename, cb)
|
2015-02-12 02:28:42 +00:00
|
|
|
if err != nil {
|
2015-02-13 19:01:25 +00:00
|
|
|
return Errorf(err, "Error uploading file %s (%s)", filename, oid)
|
2015-02-12 02:28:42 +00:00
|
|
|
}
|
|
|
|
}
|
2015-02-13 20:37:19 +00:00
|
|
|
case 202:
|
|
|
|
// the server responded with hypermedia links to upload and verify the object.
|
2015-02-13 19:01:25 +00:00
|
|
|
err = callExternalPut(oidPath, filename, linkMeta, cb)
|
2015-02-12 02:28:42 +00:00
|
|
|
if err != nil {
|
2015-02-13 19:01:25 +00:00
|
|
|
return Errorf(err, "Error uploading file %s (%s)", filename, oid)
|
2015-02-12 02:28:42 +00:00
|
|
|
}
|
|
|
|
default:
|
2015-02-12 23:18:47 +00:00
|
|
|
return Errorf(err, "Unexpected HTTP response: %d", status)
|
2015-02-12 02:28:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-02-26 01:09:53 +00:00
|
|
|
type objectResource struct {
|
|
|
|
Oid string `json:"oid,omitempty"`
|
|
|
|
Size int64 `json:"size,omitempty"`
|
|
|
|
Links map[string]*linkRelation `json:"_links,omitempty"`
|
|
|
|
}
|
|
|
|
|
2015-02-26 01:13:56 +00:00
|
|
|
var objectRelationDoesNotExist = errors.New("relation does not exist")
|
|
|
|
|
|
|
|
func (o *objectResource) NewRequest(relation, method string) (*http.Request, error) {
|
|
|
|
rel, ok := o.Rel(relation)
|
|
|
|
if !ok {
|
|
|
|
return nil, objectRelationDoesNotExist
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest(method, rel.Href, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for h, v := range rel.Header {
|
|
|
|
req.Header.Set(h, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
return req, nil
|
|
|
|
}
|
|
|
|
|
2015-02-26 01:09:53 +00:00
|
|
|
func (o *objectResource) Rel(name string) (*linkRelation, bool) {
|
|
|
|
if o.Links == nil {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
|
|
|
rel, ok := o.Links[name]
|
|
|
|
return rel, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
type linkRelation struct {
|
|
|
|
Href string `json:"href"`
|
|
|
|
Header map[string]string `json:"header,omitempty"`
|
|
|
|
}
|
|
|
|
|
2015-02-17 18:46:08 +00:00
|
|
|
func callOptions(filehash string) (int, *WrappedError) {
|
2014-08-08 17:52:58 +00:00
|
|
|
oid := filepath.Base(filehash)
|
2014-04-01 15:17:04 +00:00
|
|
|
_, err := os.Stat(filehash)
|
|
|
|
if err != nil {
|
2015-02-17 18:46:08 +00:00
|
|
|
return 0, Errorf(err, "Internal object does not exist: %s", filehash)
|
2014-04-01 15:17:04 +00:00
|
|
|
}
|
|
|
|
|
2015-01-23 22:11:10 +00:00
|
|
|
tracerx.Printf("api_options: %s", oid)
|
2015-02-17 19:00:19 +00:00
|
|
|
req, creds, err := request("OPTIONS", oid)
|
2014-04-01 15:17:04 +00:00
|
|
|
if err != nil {
|
2015-02-17 18:46:08 +00:00
|
|
|
return 0, Errorf(err, "Unable to build OPTIONS request for %s", oid)
|
2014-04-01 15:17:04 +00:00
|
|
|
}
|
|
|
|
|
2015-01-29 15:55:52 +00:00
|
|
|
res, wErr := doRequest(req, creds)
|
2014-08-08 19:38:02 +00:00
|
|
|
if wErr != nil {
|
2014-09-22 15:46:19 +00:00
|
|
|
return 0, wErr
|
2014-04-01 15:17:04 +00:00
|
|
|
}
|
|
|
|
|
2015-01-29 15:55:52 +00:00
|
|
|
return res.StatusCode, nil
|
2014-04-01 15:17:04 +00:00
|
|
|
}
|
|
|
|
|
2015-02-17 18:46:08 +00:00
|
|
|
func callPut(filehash, filename string, cb CopyCallback) *WrappedError {
|
2014-03-12 14:55:01 +00:00
|
|
|
if filename == "" {
|
|
|
|
filename = filehash
|
|
|
|
}
|
|
|
|
|
2014-08-08 17:52:58 +00:00
|
|
|
oid := filepath.Base(filehash)
|
2014-08-07 17:37:04 +00:00
|
|
|
file, err := os.Open(filehash)
|
2013-10-31 19:22:33 +00:00
|
|
|
if err != nil {
|
2015-02-17 18:46:08 +00:00
|
|
|
return Errorf(err, "Internal object does not exist: %s", filehash)
|
2013-10-31 19:22:33 +00:00
|
|
|
}
|
2014-08-07 17:37:04 +00:00
|
|
|
defer file.Close()
|
2013-10-31 19:22:33 +00:00
|
|
|
|
2014-08-07 17:37:04 +00:00
|
|
|
stat, err := file.Stat()
|
2013-10-04 17:09:03 +00:00
|
|
|
if err != nil {
|
2015-02-17 18:46:08 +00:00
|
|
|
return Errorf(err, "Internal object does not exist: %s", filehash)
|
2013-10-04 17:09:03 +00:00
|
|
|
}
|
|
|
|
|
2015-02-17 19:00:19 +00:00
|
|
|
req, creds, err := request("PUT", oid)
|
2013-10-04 17:09:03 +00:00
|
|
|
if err != nil {
|
2015-02-17 18:46:08 +00:00
|
|
|
return Errorf(err, "Unable to build PUT request for %s", oid)
|
2013-10-04 17:09:03 +00:00
|
|
|
}
|
2013-11-02 00:31:11 +00:00
|
|
|
|
2014-08-07 17:37:04 +00:00
|
|
|
fileSize := stat.Size()
|
2015-02-12 23:18:47 +00:00
|
|
|
reader := &CallbackReader{
|
2014-08-07 17:37:04 +00:00
|
|
|
C: cb,
|
|
|
|
TotalSize: fileSize,
|
|
|
|
Reader: file,
|
|
|
|
}
|
|
|
|
|
|
|
|
bar := pb.StartNew(int(fileSize))
|
2014-03-12 15:31:41 +00:00
|
|
|
bar.SetUnits(pb.U_BYTES)
|
|
|
|
bar.Start()
|
|
|
|
|
2013-12-09 15:34:29 +00:00
|
|
|
req.Header.Set("Content-Type", gitMediaType)
|
|
|
|
req.Header.Set("Accept", gitMediaMetaType)
|
2014-08-07 17:37:04 +00:00
|
|
|
req.Body = ioutil.NopCloser(bar.NewProxyReader(reader))
|
|
|
|
req.ContentLength = fileSize
|
2013-10-04 17:09:03 +00:00
|
|
|
|
2014-03-12 15:31:41 +00:00
|
|
|
fmt.Printf("Sending %s\n", filename)
|
|
|
|
|
2015-01-23 22:11:10 +00:00
|
|
|
tracerx.Printf("api_put: %s %s", oid, filename)
|
2015-03-05 18:39:37 +00:00
|
|
|
_, wErr := doRequest(req, creds)
|
2013-10-04 17:09:03 +00:00
|
|
|
|
2015-02-17 18:46:08 +00:00
|
|
|
return wErr
|
2013-10-04 17:09:03 +00:00
|
|
|
}
|
2013-10-22 18:21:01 +00:00
|
|
|
|
2015-02-26 01:09:53 +00:00
|
|
|
func callExternalPut(filehash, filename string, obj *objectResource, cb CopyCallback) *WrappedError {
|
|
|
|
if obj == nil {
|
2015-02-17 18:46:08 +00:00
|
|
|
return Errorf(errors.New("No hypermedia links provided"),
|
|
|
|
"Error attempting to PUT %s", filename)
|
2015-02-13 20:37:19 +00:00
|
|
|
}
|
|
|
|
|
2015-02-26 01:13:56 +00:00
|
|
|
req, err := obj.NewRequest("upload", "PUT")
|
|
|
|
if err == objectRelationDoesNotExist {
|
2015-02-17 18:46:08 +00:00
|
|
|
return Errorf(errors.New("No upload link provided"),
|
|
|
|
"Error attempting to PUT %s", filename)
|
2015-01-23 22:11:10 +00:00
|
|
|
}
|
|
|
|
|
2015-02-26 01:13:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return Errorf(err, "Error attempting to PUT %s", filename)
|
|
|
|
}
|
|
|
|
|
2015-01-23 22:11:10 +00:00
|
|
|
file, err := os.Open(filehash)
|
|
|
|
if err != nil {
|
2015-02-17 18:46:08 +00:00
|
|
|
return Errorf(err, "Error attempting to PUT %s", filename)
|
2015-01-23 22:11:10 +00:00
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
stat, err := file.Stat()
|
|
|
|
if err != nil {
|
2015-02-17 18:46:08 +00:00
|
|
|
return Errorf(err, "Error attempting to PUT %s", filename)
|
2015-01-23 22:11:10 +00:00
|
|
|
}
|
|
|
|
fileSize := stat.Size()
|
2015-02-12 23:18:47 +00:00
|
|
|
reader := &CallbackReader{
|
2015-01-23 22:11:10 +00:00
|
|
|
C: cb,
|
|
|
|
TotalSize: fileSize,
|
|
|
|
Reader: file,
|
|
|
|
}
|
|
|
|
|
2015-02-17 19:19:12 +00:00
|
|
|
creds, err := setRequestHeaders(req)
|
2015-02-17 19:00:19 +00:00
|
|
|
if err != nil {
|
|
|
|
return Errorf(err, "Error attempting to PUT %s", filename)
|
|
|
|
}
|
|
|
|
|
2015-01-23 22:11:10 +00:00
|
|
|
bar := pb.StartNew(int(fileSize))
|
|
|
|
bar.SetUnits(pb.U_BYTES)
|
|
|
|
bar.Start()
|
|
|
|
|
|
|
|
req.Body = ioutil.NopCloser(bar.NewProxyReader(reader))
|
|
|
|
req.ContentLength = fileSize
|
|
|
|
|
|
|
|
tracerx.Printf("external_put: %s %s", filepath.Base(filehash), req.URL)
|
2015-02-13 23:51:01 +00:00
|
|
|
res, err := DoHTTP(Config, req)
|
2015-01-23 22:11:10 +00:00
|
|
|
if err != nil {
|
2015-02-17 18:46:08 +00:00
|
|
|
return Errorf(err, "Error attempting to PUT %s", filename)
|
2015-01-23 22:11:10 +00:00
|
|
|
}
|
2015-02-17 19:00:19 +00:00
|
|
|
saveCredentials(creds, res)
|
2015-01-23 22:11:10 +00:00
|
|
|
|
2015-02-13 20:37:19 +00:00
|
|
|
// Run the verify callback
|
2015-02-26 01:13:56 +00:00
|
|
|
verifyReq, err := obj.NewRequest("verify", "POST")
|
|
|
|
if err == objectRelationDoesNotExist {
|
|
|
|
return nil
|
|
|
|
}
|
2015-02-17 19:00:19 +00:00
|
|
|
|
2015-02-26 01:13:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return Errorf(err, "Error attempting to verify %s", filename)
|
|
|
|
}
|
2015-02-17 19:00:19 +00:00
|
|
|
|
2015-02-26 01:13:56 +00:00
|
|
|
verifyCreds, err := setRequestHeaders(verifyReq)
|
|
|
|
if err != nil {
|
|
|
|
return Errorf(err, "Error attempting to verify %s", filename)
|
|
|
|
}
|
2015-01-23 22:11:10 +00:00
|
|
|
|
2015-02-26 01:13:56 +00:00
|
|
|
oid := filepath.Base(filehash)
|
|
|
|
d := fmt.Sprintf(`{"oid":"%s", "size":%d}`, oid, fileSize)
|
|
|
|
verifyReq.Body = ioutil.NopCloser(bytes.NewBufferString(d))
|
2015-01-23 22:11:10 +00:00
|
|
|
|
2015-02-26 01:13:56 +00:00
|
|
|
tracerx.Printf("verify: %s %s", oid, verifyReq.URL.String())
|
|
|
|
verifyRes, err := DoHTTP(Config, verifyReq)
|
|
|
|
if err != nil {
|
|
|
|
return Errorf(err, "Error attempting to verify %s", filename)
|
2015-01-23 22:11:10 +00:00
|
|
|
}
|
2015-02-26 01:13:56 +00:00
|
|
|
saveCredentials(verifyCreds, verifyRes)
|
2015-01-23 22:11:10 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-02-26 01:09:53 +00:00
|
|
|
func callPost(filehash, filename string) (*objectResource, int, *WrappedError) {
|
2015-01-23 22:11:10 +00:00
|
|
|
oid := filepath.Base(filehash)
|
2015-02-17 19:00:19 +00:00
|
|
|
req, creds, err := request("POST", "")
|
2015-01-23 22:11:10 +00:00
|
|
|
if err != nil {
|
2015-02-17 18:46:08 +00:00
|
|
|
return nil, 0, Errorf(err, "Error attempting to POST %s", filename)
|
2015-01-23 22:11:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
file, err := os.Open(filehash)
|
|
|
|
if err != nil {
|
2015-02-17 18:46:08 +00:00
|
|
|
return nil, 0, Errorf(err, "Error attempting to POST %s", filename)
|
2015-01-23 22:11:10 +00:00
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
stat, err := file.Stat()
|
|
|
|
if err != nil {
|
2015-02-17 18:46:08 +00:00
|
|
|
return nil, 0, Errorf(err, "Error attempting to POST %s", filename)
|
2015-01-23 22:11:10 +00:00
|
|
|
}
|
|
|
|
fileSize := stat.Size()
|
|
|
|
|
|
|
|
d := fmt.Sprintf(`{"oid":"%s", "size":%d}`, oid, fileSize)
|
|
|
|
req.Body = ioutil.NopCloser(bytes.NewBufferString(d))
|
|
|
|
|
|
|
|
req.Header.Set("Accept", gitMediaMetaType)
|
|
|
|
|
|
|
|
tracerx.Printf("api_post: %s %s", oid, filename)
|
|
|
|
res, wErr := doRequest(req, creds)
|
|
|
|
if wErr != nil {
|
|
|
|
return nil, 0, wErr
|
|
|
|
}
|
|
|
|
|
2015-02-13 20:37:19 +00:00
|
|
|
if res.StatusCode == 202 {
|
2015-02-26 01:09:53 +00:00
|
|
|
obj := &objectResource{}
|
|
|
|
err := json.NewDecoder(res.Body).Decode(obj)
|
2015-01-23 22:11:10 +00:00
|
|
|
if err != nil {
|
2015-02-12 23:18:47 +00:00
|
|
|
return nil, res.StatusCode, Errorf(err, "Error decoding JSON from %s %s.", req.Method, req.URL)
|
2015-01-23 22:11:10 +00:00
|
|
|
}
|
|
|
|
|
2015-02-26 01:09:53 +00:00
|
|
|
return obj, res.StatusCode, nil
|
2015-01-23 22:11:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil, res.StatusCode, nil
|
|
|
|
}
|
|
|
|
|
2015-02-13 23:30:47 +00:00
|
|
|
func validateMediaHeader(contentType string, reader io.Reader) (bool, int, *WrappedError) {
|
2014-04-16 15:02:58 +00:00
|
|
|
mediaType, params, err := mime.ParseMediaType(contentType)
|
2015-02-13 23:30:47 +00:00
|
|
|
var headerSize int
|
|
|
|
|
2014-04-16 15:02:58 +00:00
|
|
|
if err != nil {
|
2015-02-13 23:30:47 +00:00
|
|
|
return false, headerSize, Errorf(err, "Invalid Media Type: %s", contentType)
|
2014-04-16 15:02:58 +00:00
|
|
|
}
|
2014-03-27 14:59:32 +00:00
|
|
|
|
2015-01-23 22:11:10 +00:00
|
|
|
if mediaType == gitMediaType {
|
2014-03-27 14:59:32 +00:00
|
|
|
|
2015-01-23 22:11:10 +00:00
|
|
|
givenHeader, ok := params["header"]
|
|
|
|
if !ok {
|
2015-02-13 23:30:47 +00:00
|
|
|
return false, headerSize, Error(fmt.Errorf("Missing Git Media header in %s", contentType))
|
2015-01-23 22:11:10 +00:00
|
|
|
}
|
2013-10-22 18:21:01 +00:00
|
|
|
|
2015-01-23 22:11:10 +00:00
|
|
|
fullGivenHeader := "--" + givenHeader + "\n"
|
2015-02-13 23:30:47 +00:00
|
|
|
headerSize = len(fullGivenHeader)
|
2014-04-16 15:02:58 +00:00
|
|
|
|
2015-02-13 23:30:47 +00:00
|
|
|
header := make([]byte, headerSize)
|
2015-01-23 22:11:10 +00:00
|
|
|
_, err = io.ReadAtLeast(reader, header, len(fullGivenHeader))
|
|
|
|
if err != nil {
|
2015-02-13 23:30:47 +00:00
|
|
|
return false, headerSize, Errorf(err, "Error reading response body.")
|
2015-01-23 22:11:10 +00:00
|
|
|
}
|
2014-04-16 15:02:58 +00:00
|
|
|
|
2015-01-23 22:11:10 +00:00
|
|
|
if string(header) != fullGivenHeader {
|
2015-02-13 23:30:47 +00:00
|
|
|
return false, headerSize, Error(fmt.Errorf("Invalid header: %s expected, got %s", fullGivenHeader, header))
|
2015-01-23 22:11:10 +00:00
|
|
|
}
|
2014-04-16 15:02:58 +00:00
|
|
|
}
|
2015-02-13 23:30:47 +00:00
|
|
|
return true, headerSize, nil
|
2013-10-22 18:21:01 +00:00
|
|
|
}
|
|
|
|
|
2015-02-12 23:18:47 +00:00
|
|
|
func doRequest(req *http.Request, creds Creds) (*http.Response, *WrappedError) {
|
2015-02-13 23:51:01 +00:00
|
|
|
res, err := DoHTTP(Config, req)
|
2013-11-02 00:31:11 +00:00
|
|
|
|
2015-02-12 23:18:47 +00:00
|
|
|
var wErr *WrappedError
|
2014-08-08 17:31:33 +00:00
|
|
|
|
2015-02-12 23:18:47 +00:00
|
|
|
if err == RedirectError {
|
2015-02-01 23:34:59 +00:00
|
|
|
err = nil
|
|
|
|
}
|
|
|
|
|
2013-11-02 00:23:37 +00:00
|
|
|
if err == nil {
|
2015-02-17 18:46:08 +00:00
|
|
|
if creds != nil {
|
|
|
|
saveCredentials(creds, res)
|
2013-11-02 00:23:37 +00:00
|
|
|
}
|
2015-02-17 18:46:08 +00:00
|
|
|
|
|
|
|
wErr = handleResponseError(res)
|
2015-02-01 23:34:59 +00:00
|
|
|
} else if res.StatusCode != 302 { // hack for pre-release
|
2015-02-12 23:18:47 +00:00
|
|
|
wErr = Errorf(err, "Error sending HTTP request to %s", req.URL.String())
|
2014-08-08 17:31:33 +00:00
|
|
|
}
|
2014-07-28 23:46:12 +00:00
|
|
|
|
2014-08-08 17:31:33 +00:00
|
|
|
if wErr != nil {
|
|
|
|
if res != nil {
|
|
|
|
setErrorResponseContext(wErr, res)
|
|
|
|
} else {
|
|
|
|
setErrorRequestContext(wErr, req)
|
|
|
|
}
|
2013-11-02 00:23:37 +00:00
|
|
|
}
|
2013-11-02 00:31:11 +00:00
|
|
|
|
2014-08-08 17:31:33 +00:00
|
|
|
return res, wErr
|
|
|
|
}
|
|
|
|
|
2015-02-17 18:46:08 +00:00
|
|
|
func handleResponseError(res *http.Response) *WrappedError {
|
|
|
|
if res.StatusCode < 400 || res.StatusCode == 405 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var wErr *WrappedError
|
|
|
|
apiErr := &ClientError{}
|
|
|
|
dec := json.NewDecoder(res.Body)
|
|
|
|
if err := dec.Decode(apiErr); err != nil {
|
|
|
|
wErr = Errorf(err, "Error decoding JSON from response")
|
|
|
|
} else {
|
|
|
|
var msg string
|
|
|
|
switch res.StatusCode {
|
|
|
|
case 401, 403:
|
|
|
|
msg = fmt.Sprintf("Authorization error: %s\nCheck that you have proper access to the repository.", res.Request.URL)
|
|
|
|
case 404:
|
|
|
|
msg = fmt.Sprintf("Repository not found: %s\nCheck that it exists and that you have proper access to it.", res.Request.URL)
|
|
|
|
default:
|
|
|
|
msg = fmt.Sprintf("Invalid response: %d", res.StatusCode)
|
|
|
|
}
|
|
|
|
|
|
|
|
wErr = Errorf(apiErr, msg)
|
|
|
|
}
|
|
|
|
|
|
|
|
if res.StatusCode < 500 {
|
|
|
|
wErr.Panic = false
|
|
|
|
}
|
|
|
|
|
|
|
|
return wErr
|
|
|
|
}
|
|
|
|
|
|
|
|
func saveCredentials(creds Creds, res *http.Response) {
|
2015-02-17 19:00:19 +00:00
|
|
|
if creds == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-02-17 18:46:08 +00:00
|
|
|
if res.StatusCode < 300 {
|
|
|
|
execCreds(creds, "approve")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if res.StatusCode < 405 {
|
|
|
|
execCreds(creds, "reject")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-08 20:02:44 +00:00
|
|
|
var hiddenHeaders = map[string]bool{
|
|
|
|
"Authorization": true,
|
|
|
|
}
|
|
|
|
|
2015-02-12 23:18:47 +00:00
|
|
|
func setErrorRequestContext(err *WrappedError, req *http.Request) {
|
|
|
|
err.Set("Endpoint", Config.Endpoint())
|
2014-08-08 20:02:44 +00:00
|
|
|
err.Set("URL", fmt.Sprintf("%s %s", req.Method, req.URL.String()))
|
|
|
|
setErrorHeaderContext(err, "Response", req.Header)
|
2014-08-08 17:31:33 +00:00
|
|
|
}
|
|
|
|
|
2015-02-12 23:18:47 +00:00
|
|
|
func setErrorResponseContext(err *WrappedError, res *http.Response) {
|
2014-08-08 17:31:33 +00:00
|
|
|
err.Set("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
|
|
|
}
|
|
|
|
|
2015-02-12 23:18:47 +00:00
|
|
|
func setErrorHeaderContext(err *WrappedError, 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 {
|
|
|
|
err.Set(contextKey, "--")
|
|
|
|
} else {
|
|
|
|
err.Set(contextKey, head.Get(key))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-17 19:00:19 +00:00
|
|
|
func request(method, oid string) (*http.Request, Creds, error) {
|
2015-02-12 23:25:18 +00:00
|
|
|
u := Config.ObjectUrl(oid)
|
2013-10-31 22:12:30 +00:00
|
|
|
req, err := http.NewRequest(method, u.String(), nil)
|
2015-02-17 19:16:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return req, nil, err
|
2013-10-31 22:12:30 +00:00
|
|
|
}
|
|
|
|
|
2015-02-17 19:19:12 +00:00
|
|
|
creds, err := setRequestHeaders(req)
|
2015-02-17 19:16:59 +00:00
|
|
|
return req, creds, err
|
2013-10-31 21:33:57 +00:00
|
|
|
}
|
|
|
|
|
2015-02-17 19:19:12 +00:00
|
|
|
func setRequestHeaders(req *http.Request) (Creds, error) {
|
|
|
|
req.Header.Set("User-Agent", UserAgent)
|
|
|
|
|
2015-02-17 19:00:19 +00:00
|
|
|
if _, ok := req.Header["Authorization"]; ok {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
creds, err := credentials(req.URL)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
token := fmt.Sprintf("%s:%s", creds["username"], creds["password"])
|
|
|
|
auth := "Basic " + base64.URLEncoding.EncodeToString([]byte(token))
|
|
|
|
req.Header.Set("Authorization", auth)
|
|
|
|
return creds, nil
|
|
|
|
}
|
|
|
|
|
2015-02-12 23:18:47 +00:00
|
|
|
type ClientError struct {
|
2013-10-31 19:00:09 +00:00
|
|
|
Message string `json:"message"`
|
|
|
|
RequestId string `json:"request_id,omitempty"`
|
|
|
|
}
|
|
|
|
|
2015-02-12 23:18:47 +00:00
|
|
|
func (e *ClientError) Error() string {
|
2013-10-31 19:00:09 +00:00
|
|
|
return e.Message
|
|
|
|
}
|