From 1be680fdf06b94d61987c2ffc1e65bc203ca7bc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 15 Apr 2017 20:10:19 +0200 Subject: [PATCH] Add a managed HTTP(S) transport implementation This implements a "smart subtransport" which lets us implement just the I/O portion and leave the Git Smart Protocol logic in the library. --- git.go | 8 ++ http.go | 315 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ wrapper.c | 34 ++++++ 3 files changed, 357 insertions(+) create mode 100644 http.go diff --git a/git.go b/git.go index 0925e45..077d04b 100644 --- a/git.go +++ b/git.go @@ -129,6 +129,14 @@ func init() { panic("libgit2 was not built with threading support") } + if err := RegisterManagedHttp(); err != nil { + panic(err) + } + + if err := RegisterManagedHttps(); err != nil { + panic(err) + } + // This is not something we should be doing, as we may be // stomping all over someone else's setup. The user should do // this themselves or use some binding/wrapper which does it diff --git a/http.go b/http.go new file mode 100644 index 0000000..c32fc18 --- /dev/null +++ b/http.go @@ -0,0 +1,315 @@ +package git + +/* +#include +#include + +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/ioutil" + "net/http" + "reflect" + "runtime" + //"runtime/debug" + "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{} + +func (self *ManagedTransport) Action(url string, action SmartService) (SmartSubtransportStream, error) { + 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(req), nil +} + +func (self *ManagedTransport) Close() error { + return nil +} + +func (self *ManagedTransport) Free() { +} + +type ManagedHttpStream struct { + req *http.Request + resp *http.Response + postBuffer bytes.Buffer + sentRequest bool +} + +func newManagedHttpStream(req *http.Request) *ManagedHttpStream { + return &ManagedHttpStream{ + 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 { + self.req.Body = ioutil.NopCloser(&self.postBuffer) + resp, err := http.DefaultClient.Do(self.req) + if err != nil { + return err + } + + 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) + + 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{} + 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 unsafe.Pointer, 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(data) + + n, err := stream.Read(p) + if err != nil { + return setLibgit2Error(err) + } + + *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) { +} diff --git a/wrapper.c b/wrapper.c index 11c2f32..d498284 100644 --- a/wrapper.c +++ b/wrapper.c @@ -2,6 +2,7 @@ #include #include #include +#include typedef int (*gogit_submodule_cbk)(git_submodule *sm, const char *name, void *payload); @@ -180,4 +181,37 @@ void _go_git_writestream_free(git_writestream *stream) stream->free(stream); } +int _go_git_transport_register(const char *scheme) +{ + return git_transport_register(scheme, httpTransportCb, NULL); +} + +int _go_git_transport_smart(git_transport **out, git_remote *owner) +{ + git_smart_subtransport_definition defn = { + httpSmartSubtransportCb, + 1, // RPC/Stateless + }; + + return git_transport_smart(out, owner, &defn); +} + +void _go_git_setup_smart_subtransport(managed_smart_subtransport *t, void *ptr) +{ + t->parent.action = httpAction; + t->parent.close = httpClose; + t->parent.free = httpFree; + + t->ptr = ptr; +} + +void _go_git_setup_smart_subtransport_stream(managed_smart_subtransport_stream *s, void *ptr) +{ + s->parent.read = smartSubtransportRead; + s->parent.write = smartSubtransportWrite; + s->parent.free = smartSubtransportFree; + + s->ptr = ptr; +} + /* EOF */