git-lfs/gitmediaclient/client.go

212 lines
4.4 KiB
Go
Raw Normal View History

2013-10-04 17:09:03 +00:00
package gitmediaclient
import (
"encoding/base64"
"encoding/json"
"errors"
2013-10-04 17:09:03 +00:00
"fmt"
"github.com/cheggaaa/pb"
"github.com/github/git-media/gitmedia"
"io"
"io/ioutil"
2014-04-16 14:30:13 +00:00
"mime"
2013-10-04 17:09:03 +00:00
"net/http"
"net/url"
2013-10-04 17:09:03 +00:00
"os"
"path/filepath"
)
const (
2014-04-16 15:28:04 +00:00
gitMediaType = "application/vnd.git-media"
gitMediaMetaType = gitMediaType + "+json; charset=utf-8"
gitMediaHeader = "--git-media."
)
func Options(filehash string) error {
oid := filepath.Base(filehash)
_, err := os.Stat(filehash)
if err != nil {
return err
}
req, creds, err := clientRequest("OPTIONS", oid)
if err != nil {
return err
}
_, err = doRequest(req, creds)
if err != nil {
return err
}
return nil
}
func Put(filehash, filename string, cb gitmedia.CopyCallback) error {
2014-03-12 14:55:01 +00:00
if filename == "" {
filename = filehash
}
oid := filepath.Base(filehash)
file, err := os.Open(filehash)
2013-10-31 19:22:33 +00:00
if err != nil {
return err
}
defer file.Close()
2013-10-31 19:22:33 +00:00
stat, err := file.Stat()
2013-10-04 17:09:03 +00:00
if err != nil {
return err
}
req, creds, err := clientRequest("PUT", oid)
2013-10-04 17:09:03 +00:00
if err != nil {
return err
}
fileSize := stat.Size()
reader := &gitmedia.CallbackReader{
C: cb,
TotalSize: fileSize,
Reader: file,
}
bar := pb.StartNew(int(fileSize))
bar.SetUnits(pb.U_BYTES)
bar.Start()
req.Header.Set("Content-Type", gitMediaType)
req.Header.Set("Accept", gitMediaMetaType)
req.Body = ioutil.NopCloser(bar.NewProxyReader(reader))
req.ContentLength = fileSize
2013-10-04 17:09:03 +00:00
fmt.Printf("Sending %s\n", filename)
_, err = doRequest(req, creds)
2013-10-04 17:09:03 +00:00
if err != nil {
return err
}
return nil
}
func Get(filename string) (io.ReadCloser, int64, *gitmedia.WrappedError) {
2013-10-31 21:33:57 +00:00
oid := filepath.Base(filename)
req, creds, err := clientRequest("GET", oid)
if err != nil {
return nil, 0, gitmedia.Error(err)
}
req.Header.Set("Accept", gitMediaType)
2014-08-08 17:05:24 +00:00
res, wErr := doRequest(req, creds)
if err != nil {
2014-08-08 17:05:24 +00:00
return nil, 0, wErr
}
2014-04-16 14:30:13 +00:00
contentType := res.Header.Get("Content-Type")
if contentType == "" {
return nil, 0, gitmedia.Error(errors.New("Invalid Content-Type"))
}
2014-04-16 14:30:13 +00:00
if ok, err := validateMediaHeader(contentType, res.Body); !ok {
return nil, 0, gitmedia.Error(err)
}
2014-04-16 14:30:13 +00:00
return res.Body, res.ContentLength, nil
}
2014-04-16 14:30:13 +00:00
func validateMediaHeader(contentType string, reader io.Reader) (bool, error) {
mediaType, params, err := mime.ParseMediaType(contentType)
if err != nil {
return false, errors.New("Invalid Media Type")
}
if mediaType != gitMediaType {
return false, errors.New("Invalid Media Type")
}
givenHeader, ok := params["header"]
if !ok {
return false, errors.New("Invalid header")
}
fullGivenHeader := "--" + givenHeader + "\n"
header := make([]byte, len(fullGivenHeader))
_, err = io.ReadAtLeast(reader, header, len(fullGivenHeader))
if err != nil {
return false, err
}
if string(header) != fullGivenHeader {
return false, errors.New("Invalid header")
}
return true, nil
}
2014-08-08 17:05:24 +00:00
func doRequest(req *http.Request, creds Creds) (*http.Response, *gitmedia.WrappedError) {
2013-11-02 00:23:37 +00:00
res, err := http.DefaultClient.Do(req)
2013-11-02 00:23:37 +00:00
if err == nil {
if res.StatusCode > 299 {
// An auth error should be 403. Could be 404 also.
if res.StatusCode < 405 {
execCreds(creds, "reject")
}
2013-11-02 00:23:37 +00:00
apierr := &Error{}
dec := json.NewDecoder(res.Body)
if err := dec.Decode(apierr); err != nil {
2014-08-08 17:05:24 +00:00
return res, gitmedia.Errorf(err, "Error decoding JSON from response")
2013-11-02 00:23:37 +00:00
}
2014-08-08 17:05:24 +00:00
return res, gitmedia.Errorf(apierr, "Invalid response: %d", res.StatusCode)
2013-11-02 00:23:37 +00:00
}
execCreds(creds, "approve")
2013-11-02 00:23:37 +00:00
}
2014-08-08 17:05:24 +00:00
wErr := gitmedia.Errorf(err, "Error sending HTTP request to %s", req.URL.String())
wErr.Set("URL", req.URL.String())
for key, _ := range req.Header {
wErr.Set(key, req.Header.Get(key))
}
return res, wErr
2013-11-02 00:23:37 +00:00
}
func clientRequest(method, oid string) (*http.Request, Creds, error) {
u := ObjectUrl(oid)
req, err := http.NewRequest(method, u.String(), nil)
if err == nil {
creds, err := credentials(u)
if err != nil {
2013-11-01 23:19:04 +00:00
return req, nil, err
}
token := fmt.Sprintf("%s:%s", creds["username"], creds["password"])
auth := "Basic " + base64.URLEncoding.EncodeToString([]byte(token))
req.Header.Set("Authorization", auth)
2013-11-01 23:19:04 +00:00
return req, creds, nil
}
2013-11-01 23:19:04 +00:00
return req, nil, err
2013-10-31 21:33:57 +00:00
}
func ObjectUrl(oid string) *url.URL {
c := gitmedia.Config
u, _ := url.Parse(c.Endpoint())
2013-11-05 17:07:03 +00:00
u.Path = filepath.Join(u.Path, "/objects/"+oid)
return u
}
type Error struct {
Message string `json:"message"`
RequestId string `json:"request_id,omitempty"`
}
func (e *Error) Error() string {
return e.Message
}