blob: 9de001b68fb705260561649947aa913c6eb4db85 [file] [log] [blame]
package git
/*
#include <git2.h>
#include <git2/sys/transport.h>
typedef struct {
git_smart_subtransport parent;
void *ptr;
} managed_smart_subtransport;
typedef struct {
git_smart_subtransport_stream parent;
void *ptr;
} managed_smart_subtransport_stream;
int _go_git_transport_register(const char *scheme);
int _go_git_transport_smart(git_transport **out, git_remote *owner);
void _go_git_setup_smart_subtransport(managed_smart_subtransport *t, void *ptr);
void _go_git_setup_smart_subtransport_stream(managed_smart_subtransport_stream *t, void *ptr);
*/
import "C"
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"runtime"
"unsafe"
)
type SmartService int
const (
SmartServiceUploadpackLs = C.GIT_SERVICE_UPLOADPACK_LS
SmartServiceUploadpack = C.GIT_SERVICE_UPLOADPACK
SmartServiceReceivepackLs = C.GIT_SERVICE_RECEIVEPACK_LS
SmartServiceReceivepack = C.GIT_SERVICE_RECEIVEPACK
)
type SmartSubtransport interface {
Action(url string, action SmartService) (SmartSubtransportStream, error)
Close() error
Free()
}
type SmartSubtransportStream interface {
Read(buf []byte) (int, error)
Write(buf []byte) error
Free()
}
func RegisterManagedHttp() error {
httpStr := C.CString("http")
defer C.free(unsafe.Pointer(httpStr))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C._go_git_transport_register(httpStr)
if ret != 0 {
return MakeGitError(ret)
}
return nil
}
func RegisterManagedHttps() error {
httpsStr := C.CString("https")
defer C.free(unsafe.Pointer(httpsStr))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C._go_git_transport_register(httpsStr)
if ret != 0 {
return MakeGitError(ret)
}
return nil
}
type ManagedTransport struct {
owner *C.git_transport
client *http.Client
}
func (self *ManagedTransport) Action(url string, action SmartService) (SmartSubtransportStream, error) {
if err := self.ensureClient(); err != nil {
return nil, err
}
var req *http.Request
var err error
switch action {
case SmartServiceUploadpackLs:
req, err = http.NewRequest("GET", url+"/info/refs?service=git-upload-pack", nil)
case SmartServiceUploadpack:
req, err = http.NewRequest("POST", url+"/git-upload-pack", nil)
if err != nil {
break
}
req.Header["Content-Type"] = []string{"application/x-git-upload-pack-request"}
case SmartServiceReceivepackLs:
req, err = http.NewRequest("GET", url+"/info/refs?service=git-receive-pack", nil)
case SmartServiceReceivepack:
req, err = http.NewRequest("POST", url+"/info/refs?service=git-upload-pack", nil)
if err != nil {
break
}
req.Header["Content-Type"] = []string{"application/x-git-receive-pack-request"}
default:
err = errors.New("unknown action")
}
if err != nil {
return nil, err
}
req.Header["User-Agent"] = []string{"git/2.0 (git2go)"}
return newManagedHttpStream(self, req), nil
}
func (self *ManagedTransport) Close() error {
self.client = nil
return nil
}
func (self *ManagedTransport) Free() {
}
func (self *ManagedTransport) ensureClient() error {
if self.client != nil {
return nil
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var cpopts C.git_proxy_options
if ret := C.git_transport_smart_proxy_options(&cpopts, self.owner); ret < 0 {
return MakeGitError(ret)
}
var proxyFn func(*http.Request) (*url.URL, error)
proxyOpts := proxyOptionsFromC(&cpopts)
switch proxyOpts.Type {
case ProxyTypeNone:
proxyFn = nil
case ProxyTypeAuto:
proxyFn = http.ProxyFromEnvironment
case ProxyTypeSpecified:
parsedUrl, err := url.Parse(proxyOpts.Url)
if err != nil {
return err
}
proxyFn = http.ProxyURL(parsedUrl)
}
transport := &http.Transport{
Proxy: proxyFn,
}
self.client = &http.Client{Transport: transport}
return nil
}
type ManagedHttpStream struct {
owner *ManagedTransport
req *http.Request
resp *http.Response
postBuffer bytes.Buffer
sentRequest bool
}
func newManagedHttpStream(owner *ManagedTransport, req *http.Request) *ManagedHttpStream {
return &ManagedHttpStream{
owner: owner,
req: req,
}
}
func (self *ManagedHttpStream) Read(buf []byte) (int, error) {
if !self.sentRequest {
if err := self.sendRequest(); err != nil {
return 0, err
}
}
return self.resp.Body.Read(buf)
}
func (self *ManagedHttpStream) Write(buf []byte) error {
// We write it all into a buffer and send it off when the transport asks
// us to read.
self.postBuffer.Write(buf)
return nil
}
func (self *ManagedHttpStream) Free() {
self.resp.Body.Close()
}
func (self *ManagedHttpStream) sendRequest() error {
var resp *http.Response
var err error
var userName string
var password string
for {
req := &http.Request{
Method: self.req.Method,
URL: self.req.URL,
Header: self.req.Header,
Body: ioutil.NopCloser(&self.postBuffer),
ContentLength: int64(self.postBuffer.Len()),
}
req.SetBasicAuth(userName, password)
resp, err = http.DefaultClient.Do(req)
if err != nil {
return err
}
if resp.StatusCode == http.StatusOK {
break
}
if resp.StatusCode == http.StatusUnauthorized {
resp.Body.Close()
var cred *C.git_cred
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_transport_smart_credentials(&cred, self.owner.owner, nil, C.GIT_CREDTYPE_USERPASS_PLAINTEXT)
if ret != 0 {
return MakeGitError(ret)
}
if cred.credtype != C.GIT_CREDTYPE_USERPASS_PLAINTEXT {
C.git_cred_free(cred)
return fmt.Errorf("Unexpected credential type %d", cred.credtype)
}
ptCred := (*C.git_cred_userpass_plaintext)(unsafe.Pointer(cred))
userName = C.GoString(ptCred.username)
password = C.GoString(ptCred.password)
C.git_cred_free(cred)
continue
}
// Any other error we treat as a hard error and punt back to the caller
resp.Body.Close()
return fmt.Errorf("Unhandled HTTP error %s", resp.Status)
}
self.sentRequest = true
self.resp = resp
return nil
}
func setLibgit2Error(err error) C.int {
cstr := C.CString(err.Error())
defer C.free(unsafe.Pointer(cstr))
C.giterr_set_str(C.GITERR_NET, cstr)
if gitErr, ok := err.(*GitError); ok {
return C.int(gitErr.Code)
}
return -1
}
//export httpAction
func httpAction(out **C.git_smart_subtransport_stream, t *C.git_smart_subtransport, url *C.char, action C.git_smart_service_t) C.int {
transport, err := getSmartSubtransportInterface(t)
if err != nil {
return setLibgit2Error(err)
}
managed, err := transport.Action(C.GoString(url), SmartService(action))
if err != nil {
return setLibgit2Error(err)
}
stream := C.calloc(1, C.size_t(unsafe.Sizeof(C.managed_smart_subtransport_stream{})))
managedPtr := pointerHandles.Track(managed)
C._go_git_setup_smart_subtransport_stream(stream, managedPtr)
*out = (*C.git_smart_subtransport_stream)(stream)
return 0
}
//export httpClose
func httpClose(t *C.git_smart_subtransport) C.int {
transport, err := getSmartSubtransportInterface(t)
if err != nil {
return setLibgit2Error(err)
}
if err := transport.Close(); err != nil {
return setLibgit2Error(err)
}
return 0
}
//export httpFree
func httpFree(transport *C.git_smart_subtransport) {
wrapperPtr := (*C.managed_smart_subtransport)(unsafe.Pointer(transport))
pointerHandles.Untrack(wrapperPtr.ptr)
}
var errNoSmartSubtransport = errors.New("passed object does not implement SmartSubtransport")
func getSmartSubtransportInterface(_t *C.git_smart_subtransport) (SmartSubtransport, error) {
wrapperPtr := (*C.managed_smart_subtransport)(unsafe.Pointer(_t))
transport, ok := pointerHandles.Get(wrapperPtr.ptr).(SmartSubtransport)
if !ok {
return nil, errNoSmartSubtransport
}
return transport, nil
}
//export httpTransportCb
func httpTransportCb(out **C.git_transport, owner *C.git_remote, param unsafe.Pointer) C.int {
return C._go_git_transport_smart(out, owner)
}
//export httpSmartSubtransportCb
func httpSmartSubtransportCb(out **C.git_smart_subtransport, owner *C.git_transport, param unsafe.Pointer) C.int {
if out == nil {
return -1
}
transport := C.calloc(1, C.size_t(unsafe.Sizeof(C.managed_smart_subtransport{})))
managed := &ManagedTransport{owner: owner}
managedPtr := pointerHandles.Track(managed)
C._go_git_setup_smart_subtransport(transport, managedPtr)
*out = (*C.git_smart_subtransport)(transport)
return 0
}
var errNoSmartSubtransportStream = errors.New("passed object does not implement SmartSubtransportStream")
func getSmartSubtransportStreamInterface(_s *C.git_smart_subtransport_stream) (SmartSubtransportStream, error) {
wrapperPtr := (*C.managed_smart_subtransport_stream)(unsafe.Pointer(_s))
transport, ok := pointerHandles.Get(wrapperPtr.ptr).(SmartSubtransportStream)
if !ok {
return nil, errNoSmartSubtransportStream
}
return transport, nil
}
//export smartSubtransportRead
func smartSubtransportRead(s *C.git_smart_subtransport_stream, data *C.char, l C.size_t, read *C.size_t) C.int {
stream, err := getSmartSubtransportStreamInterface(s)
if err != nil {
return setLibgit2Error(err)
}
var p []byte
header := (*reflect.SliceHeader)(unsafe.Pointer(&p))
header.Cap = int(l)
header.Len = int(l)
header.Data = uintptr(unsafe.Pointer(data))
n, err := stream.Read(p)
if err != nil {
if err == io.EOF {
*read = C.size_t(0)
return 0
}
setLibgit2Error(err)
return -1
}
*read = C.size_t(n)
return 0
}
//export smartSubtransportWrite
func smartSubtransportWrite(s *C.git_smart_subtransport_stream, data unsafe.Pointer, l C.size_t) C.int {
stream, err := getSmartSubtransportStreamInterface(s)
if err != nil {
return setLibgit2Error(err)
}
var p []byte
header := (*reflect.SliceHeader)(unsafe.Pointer(&p))
header.Cap = int(l)
header.Len = int(l)
header.Data = uintptr(data)
if err := stream.Write(p); err != nil {
return setLibgit2Error(err)
}
return 0
}
//export smartSubtransportFree
func smartSubtransportFree(s *C.git_smart_subtransport_stream) {
}