package git /* #include #include #include typedef struct { git_smart_subtransport parent; void *handle; } _go_managed_smart_subtransport; typedef struct { git_smart_subtransport_stream parent; void *handle; } _go_managed_smart_subtransport_stream; int _go_git_transport_register(const char *prefix, void *handle); int _go_git_transport_smart(git_transport **out, git_remote *owner, int stateless, _go_managed_smart_subtransport *subtransport_payload); void _go_git_setup_smart_subtransport_stream(_go_managed_smart_subtransport_stream *stream); */ import "C" import ( "fmt" "io" "reflect" "runtime" "sync" "unsafe" ) var ( // globalRegisteredSmartTransports is a mapping of global, git2go-managed // transports. globalRegisteredSmartTransports = struct { sync.Mutex transports map[string]*RegisteredSmartTransport }{ transports: make(map[string]*RegisteredSmartTransport), } ) // unregisterManagedTransports unregisters all git2go-managed transports. func unregisterManagedTransports() error { globalRegisteredSmartTransports.Lock() originalTransports := globalRegisteredSmartTransports.transports globalRegisteredSmartTransports.transports = make(map[string]*RegisteredSmartTransport) globalRegisteredSmartTransports.Unlock() var err error for protocol, managed := range originalTransports { unregisterErr := managed.Free() if err == nil && unregisterErr != nil { err = fmt.Errorf("failed to unregister transport for %q: %v", protocol, unregisterErr) } } return err } // SmartServiceAction is an action that the smart transport can ask a // subtransport to perform. type SmartServiceAction int const ( // SmartServiceActionUploadpackLs is used upon connecting to a remote, and is // used to perform reference discovery prior to performing a pull operation. SmartServiceActionUploadpackLs SmartServiceAction = C.GIT_SERVICE_UPLOADPACK_LS // SmartServiceActionUploadpack is used when performing a pull operation. SmartServiceActionUploadpack SmartServiceAction = C.GIT_SERVICE_UPLOADPACK // SmartServiceActionReceivepackLs is used upon connecting to a remote, and is // used to perform reference discovery prior to performing a push operation. SmartServiceActionReceivepackLs SmartServiceAction = C.GIT_SERVICE_RECEIVEPACK_LS // SmartServiceActionReceivepack is used when performing a push operation. SmartServiceActionReceivepack SmartServiceAction = C.GIT_SERVICE_RECEIVEPACK ) // Transport encapsulates a way to communicate with a Remote. type Transport struct { doNotCompare ptr *C.git_transport } // SmartRemoteConnectOptions gets a copy of the proxy options for this transport. func (t *Transport) SmartRemoteConnectOptions() (*RemoteConnectOptions, error) { runtime.LockOSThread() defer runtime.UnlockOSThread() var copts C.git_remote_connect_options if ret := C.git_transport_remote_connect_options(&copts, t.ptr); ret < 0 { return nil, MakeGitError(ret) } return remoteConnectOptionsFromC(&copts), nil } // SmartCredentials calls the credentials callback for this transport. func (t *Transport) SmartCredentials(user string, methods CredentialType) (*Credential, error) { cred := newCredential() var cstr *C.char runtime.LockOSThread() defer runtime.UnlockOSThread() if user != "" { cstr = C.CString(user) defer C.free(unsafe.Pointer(cstr)) } ret := C.git_transport_smart_credentials(&cred.ptr, t.ptr, cstr, C.int(methods)) if ret != 0 { cred.Free() return nil, MakeGitError(ret) } return cred, nil } // SmartCertificateCheck calls the certificate check for this transport. func (t *Transport) SmartCertificateCheck(cert *Certificate, valid bool, hostname string) error { var ccert *C.git_cert switch cert.Kind { case CertificateHostkey: chostkeyCert := C.git_cert_hostkey{ parent: C.git_cert{ cert_type: C.GIT_CERT_HOSTKEY_LIBSSH2, }, _type: C.git_cert_ssh_t(cert.Kind), hostkey: (*C.char)(C.CBytes(cert.Hostkey.Hostkey)), hostkey_len: C.size_t(len(cert.Hostkey.Hostkey)), } defer C.free(unsafe.Pointer(chostkeyCert.hostkey)) C.memcpy(unsafe.Pointer(&chostkeyCert.hash_md5[0]), unsafe.Pointer(&cert.Hostkey.HashMD5[0]), C.size_t(len(cert.Hostkey.HashMD5))) C.memcpy(unsafe.Pointer(&chostkeyCert.hash_sha1[0]), unsafe.Pointer(&cert.Hostkey.HashSHA1[0]), C.size_t(len(cert.Hostkey.HashSHA1))) C.memcpy(unsafe.Pointer(&chostkeyCert.hash_sha256[0]), unsafe.Pointer(&cert.Hostkey.HashSHA256[0]), C.size_t(len(cert.Hostkey.HashSHA256))) if cert.Hostkey.SSHPublicKey.Type() == "ssh-rsa" { chostkeyCert.raw_type = C.GIT_CERT_SSH_RAW_TYPE_RSA } else if cert.Hostkey.SSHPublicKey.Type() == "ssh-dss" { chostkeyCert.raw_type = C.GIT_CERT_SSH_RAW_TYPE_DSS } else { chostkeyCert.raw_type = C.GIT_CERT_SSH_RAW_TYPE_UNKNOWN } ccert = (*C.git_cert)(unsafe.Pointer(&chostkeyCert)) case CertificateX509: cx509Cert := C.git_cert_x509{ parent: C.git_cert{ cert_type: C.GIT_CERT_X509, }, len: C.size_t(len(cert.X509.Raw)), data: C.CBytes(cert.X509.Raw), } defer C.free(cx509Cert.data) ccert = (*C.git_cert)(unsafe.Pointer(&cx509Cert)) } runtime.LockOSThread() defer runtime.UnlockOSThread() chostname := C.CString(hostname) defer C.free(unsafe.Pointer(chostname)) cvalid := C.int(0) if valid { cvalid = C.int(1) } ret := C.git_transport_smart_certificate_check(t.ptr, ccert, cvalid, chostname) if ret != 0 { return MakeGitError(ret) } return nil } // SmartSubtransport is the interface for custom subtransports which carry data // for the smart transport. type SmartSubtransport interface { // Action creates a SmartSubtransportStream for the provided url and // requested action. Action(url string, action SmartServiceAction) (SmartSubtransportStream, error) // Close closes the SmartSubtransport. // // Subtransports are guaranteed a call to Close between // calls to Action, except for the following two "natural" progressions // of actions against a constant URL. // // 1. UPLOADPACK_LS -> UPLOADPACK // 2. RECEIVEPACK_LS -> RECEIVEPACK Close() error // Free releases the resources of the SmartSubtransport. Free() } // SmartSubtransportStream is the interface for streams used by the smart // transport to read and write data from a subtransport. type SmartSubtransportStream interface { io.Reader io.Writer // Free releases the resources of the SmartSubtransportStream. Free() } // SmartSubtransportCallback is a function which creates a new subtransport for // the smart transport. type SmartSubtransportCallback func(remote *Remote, transport *Transport) (SmartSubtransport, error) // RegisteredSmartTransport represents a transport that has been registered. type RegisteredSmartTransport struct { doNotCompare name string stateless bool callback SmartSubtransportCallback handle unsafe.Pointer } // NewRegisteredSmartTransport adds a custom transport definition, to be used // in addition to the built-in set of transports that come with libgit2. func NewRegisteredSmartTransport( name string, stateless bool, callback SmartSubtransportCallback, ) (*RegisteredSmartTransport, error) { return newRegisteredSmartTransport(name, stateless, callback, false) } func newRegisteredSmartTransport( name string, stateless bool, callback SmartSubtransportCallback, global bool, ) (*RegisteredSmartTransport, error) { if !global { // Check if we had already registered a smart transport for this protocol. If // we had, free it. The user is now responsible for this transport for the // lifetime of the library. globalRegisteredSmartTransports.Lock() if managed, ok := globalRegisteredSmartTransports.transports[name]; ok { delete(globalRegisteredSmartTransports.transports, name) globalRegisteredSmartTransports.Unlock() err := managed.Free() if err != nil { return nil, err } } else { globalRegisteredSmartTransports.Unlock() } } cstr := C.CString(name) defer C.free(unsafe.Pointer(cstr)) runtime.LockOSThread() defer runtime.UnlockOSThread() registeredSmartTransport := &RegisteredSmartTransport{ name: name, stateless: stateless, callback: callback, } registeredSmartTransport.handle = pointerHandles.Track(registeredSmartTransport) ret := C._go_git_transport_register(cstr, registeredSmartTransport.handle) if ret != 0 { pointerHandles.Untrack(registeredSmartTransport.handle) return nil, MakeGitError(ret) } runtime.SetFinalizer(registeredSmartTransport, (*RegisteredSmartTransport).Free) return registeredSmartTransport, nil } // Free releases all resources used by the RegisteredSmartTransport and // unregisters the custom transport definition referenced by it. func (t *RegisteredSmartTransport) Free() error { runtime.LockOSThread() defer runtime.UnlockOSThread() cstr := C.CString(t.name) defer C.free(unsafe.Pointer(cstr)) if ret := C.git_transport_unregister(cstr); ret < 0 { return MakeGitError(ret) } pointerHandles.Untrack(t.handle) runtime.SetFinalizer(t, nil) t.handle = nil return nil } //export smartTransportCallback func smartTransportCallback( errorMessage **C.char, out **C.git_transport, owner *C.git_remote, handle unsafe.Pointer, ) C.int { registeredSmartTransport := pointerHandles.Get(handle).(*RegisteredSmartTransport) remote, ok := remotePointers.get(owner) if !ok { // create a new empty remote and set it // as a weak pointer, so that control stays in golang remote = createNewEmptyRemote() remote.weak = true } managed := &managedSmartSubtransport{ remote: remote, callback: registeredSmartTransport.callback, subtransport: (*C._go_managed_smart_subtransport)(C.calloc(1, C.size_t(unsafe.Sizeof(C._go_managed_smart_subtransport{})))), } managedHandle := pointerHandles.Track(managed) managed.handle = managedHandle managed.subtransport.handle = managedHandle runtime.LockOSThread() defer runtime.UnlockOSThread() ret := C._go_git_transport_smart(out, owner, cbool(registeredSmartTransport.stateless), managed.subtransport) if ret != 0 { pointerHandles.Untrack(managedHandle) } return ret } //export smartTransportSubtransportCallback func smartTransportSubtransportCallback( errorMessage **C.char, wrapperPtr *C._go_managed_smart_subtransport, owner *C.git_transport, ) C.int { subtransport := pointerHandles.Get(wrapperPtr.handle).(*managedSmartSubtransport) underlyingSmartSubtransport, err := subtransport.callback(subtransport.remote, &Transport{ptr: owner}) if err != nil { return setCallbackError(errorMessage, err) } subtransport.underlying = underlyingSmartSubtransport return C.int(ErrorCodeOK) } type managedSmartSubtransport struct { owner *C.git_transport callback SmartSubtransportCallback remote *Remote subtransport *C._go_managed_smart_subtransport underlying SmartSubtransport handle unsafe.Pointer currentManagedStream *managedSmartSubtransportStream } func getSmartSubtransportInterface(subtransport *C.git_smart_subtransport) *managedSmartSubtransport { wrapperPtr := (*C._go_managed_smart_subtransport)(unsafe.Pointer(subtransport)) return pointerHandles.Get(wrapperPtr.handle).(*managedSmartSubtransport) } //export smartSubtransportActionCallback func smartSubtransportActionCallback( errorMessage **C.char, out **C.git_smart_subtransport_stream, t *C.git_smart_subtransport, url *C.char, action C.git_smart_service_t, ) C.int { subtransport := getSmartSubtransportInterface(t) underlyingStream, err := subtransport.underlying.Action(C.GoString(url), SmartServiceAction(action)) if err != nil { return setCallbackError(errorMessage, err) } // It's okay to do strict equality here: we expect both to be identical. if subtransport.currentManagedStream == nil || subtransport.currentManagedStream.underlying != underlyingStream { runtime.LockOSThread() defer runtime.UnlockOSThread() stream := (*C._go_managed_smart_subtransport_stream)(C.calloc(1, C.size_t(unsafe.Sizeof(C._go_managed_smart_subtransport_stream{})))) managed := &managedSmartSubtransportStream{ underlying: underlyingStream, streamPtr: stream, } managedHandle := pointerHandles.Track(managed) managed.handle = managedHandle stream.handle = managedHandle C._go_git_setup_smart_subtransport_stream(stream) subtransport.currentManagedStream = managed } *out = &subtransport.currentManagedStream.streamPtr.parent return C.int(ErrorCodeOK) } //export smartSubtransportCloseCallback func smartSubtransportCloseCallback(errorMessage **C.char, t *C.git_smart_subtransport) C.int { subtransport := getSmartSubtransportInterface(t) subtransport.currentManagedStream = nil if subtransport.underlying != nil { err := subtransport.underlying.Close() if err != nil { return setCallbackError(errorMessage, err) } } return C.int(ErrorCodeOK) } //export smartSubtransportFreeCallback func smartSubtransportFreeCallback(t *C.git_smart_subtransport) { subtransport := getSmartSubtransportInterface(t) if subtransport.underlying != nil { subtransport.underlying.Free() subtransport.underlying = nil } pointerHandles.Untrack(subtransport.handle) C.free(unsafe.Pointer(subtransport.subtransport)) subtransport.handle = nil subtransport.subtransport = nil } type managedSmartSubtransportStream struct { owner *C.git_smart_subtransport_stream streamPtr *C._go_managed_smart_subtransport_stream underlying SmartSubtransportStream handle unsafe.Pointer } func getSmartSubtransportStreamInterface(subtransportStream *C.git_smart_subtransport_stream) *managedSmartSubtransportStream { managedSubtransportStream := (*C._go_managed_smart_subtransport_stream)(unsafe.Pointer(subtransportStream)) return pointerHandles.Get(managedSubtransportStream.handle).(*managedSmartSubtransportStream) } //export smartSubtransportStreamReadCallback func smartSubtransportStreamReadCallback( errorMessage **C.char, s *C.git_smart_subtransport_stream, buffer *C.char, bufSize C.size_t, bytesRead *C.size_t, ) C.int { stream := getSmartSubtransportStreamInterface(s) var p []byte header := (*reflect.SliceHeader)(unsafe.Pointer(&p)) header.Cap = int(bufSize) header.Len = int(bufSize) header.Data = uintptr(unsafe.Pointer(buffer)) n, err := stream.underlying.Read(p) *bytesRead = C.size_t(n) if n == 0 && err != nil { if err == io.EOF { return C.int(ErrorCodeOK) } return setCallbackError(errorMessage, err) } return C.int(ErrorCodeOK) } //export smartSubtransportStreamWriteCallback func smartSubtransportStreamWriteCallback( errorMessage **C.char, s *C.git_smart_subtransport_stream, buffer *C.char, bufLen C.size_t, ) C.int { stream := getSmartSubtransportStreamInterface(s) var p []byte header := (*reflect.SliceHeader)(unsafe.Pointer(&p)) header.Cap = int(bufLen) header.Len = int(bufLen) header.Data = uintptr(unsafe.Pointer(buffer)) if _, err := stream.underlying.Write(p); err != nil { return setCallbackError(errorMessage, err) } return C.int(ErrorCodeOK) } //export smartSubtransportStreamFreeCallback func smartSubtransportStreamFreeCallback(s *C.git_smart_subtransport_stream) { stream := getSmartSubtransportStreamInterface(s) stream.underlying.Free() pointerHandles.Untrack(stream.handle) C.free(unsafe.Pointer(stream.streamPtr)) stream.handle = nil stream.streamPtr = nil }