diff --git a/src/curl_stream.c b/src/curl_stream.c new file mode 100644 index 000000000..fb7a61341 --- /dev/null +++ b/src/curl_stream.c @@ -0,0 +1,219 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifdef GIT_CURL + +#include + +#include "stream.h" +#include "git2/transport.h" +#include "buffer.h" + +typedef struct { + git_stream parent; + CURL *handle; + curl_socket_t socket; + char curl_error[CURL_ERROR_SIZE + 1]; + git_cert_x509 cert_info; +} curl_stream; + +static int seterr_curl(curl_stream *s) +{ + giterr_set(GITERR_NET, "curl error: %s\n", s->curl_error); + return -1; +} + +static int curls_connect(git_stream *stream) +{ + curl_stream *s = (curl_stream *) stream; + long sockextr; + int failed_cert = 0; + CURLcode res; + res = curl_easy_perform(s->handle); + + if (res != CURLE_OK && res != CURLE_PEER_FAILED_VERIFICATION) + return seterr_curl(s); + if (res == CURLE_PEER_FAILED_VERIFICATION) + failed_cert = 1; + + if ((res = curl_easy_getinfo(s->handle, CURLINFO_LASTSOCKET, &sockextr)) != CURLE_OK) + return seterr_curl(s); + + s->socket = sockextr; + + if (s->parent.encrypted && failed_cert) + return GIT_ECERTIFICATE; + + return 0; +} + +static int curls_certificate(git_cert **out, git_stream *stream) +{ + curl_stream *s = (curl_stream *) stream; + + s->cert_info.cert_type = GIT_CERT_X509; + s->cert_info.data = NULL; + s->cert_info.len = 0; + + *out = (git_cert *) &s->cert_info; + + return 0; +} + +static int wait_for(curl_socket_t fd, bool reading) +{ + int ret; + fd_set infd, outfd, errfd; + + FD_ZERO(&infd); + FD_ZERO(&outfd); + FD_ZERO(&errfd); + + FD_SET(fd, &errfd); + if (reading) + FD_SET(fd, &infd); + else + FD_SET(fd, &outfd); + + if ((ret = select(fd + 1, &infd, &outfd, &errfd, NULL)) < 0) { + giterr_set(GITERR_OS, "error in select"); + return -1; + } + + return 0; +} + +static ssize_t curls_write(git_stream *stream, const char *data, size_t len, int flags) +{ + int error; + size_t off = 0, sent; + CURLcode res; + curl_stream *s = (curl_stream *) stream; + + GIT_UNUSED(flags); + + do { + if ((error = wait_for(s->socket, false)) < 0) + return error; + + res = curl_easy_send(s->handle, data + off, len - off, &sent); + if (res == CURLE_OK) + off += sent; + } while ((res == CURLE_OK || res == CURLE_AGAIN) && off < len); + + if (res != CURLE_OK) + return seterr_curl(s); + + return len; +} + +static ssize_t curls_read(git_stream *stream, void *data, size_t len) +{ + int error; + size_t read; + CURLcode res; + curl_stream *s = (curl_stream *) stream; + + do { + if ((error = wait_for(s->socket, true)) < 0) + return error; + + res = curl_easy_recv(s->handle, data, len, &read); + } while (res == CURLE_AGAIN); + + if (res != CURLE_OK) + return seterr_curl(s); + + return read; +} + +static int curls_close(git_stream *stream) +{ + curl_stream *s = (curl_stream *) stream; + + if (!s->handle) + return 0; + + curl_easy_cleanup(s->handle); + s->handle = NULL; + s->socket = 0; + + return 0; +} + +static void curls_free(git_stream *stream) +{ + curl_stream *s = (curl_stream *) stream; + + curls_close(stream); + git__free(s); +} + +int git_curl_stream_new(git_stream **out, const char *host, const char *port, int encrypted) +{ + curl_stream *st; + CURL *handle; + int iport = 0, error; + + st = git__calloc(1, sizeof(curl_stream)); + GITERR_CHECK_ALLOC(st); + + handle = curl_easy_init(); + if (handle == NULL) { + giterr_set(GITERR_NET, "failed to create curl handle"); + return -1; + } + + if ((error = git__strtol32(&iport, port, NULL, 10)) < 0) + return error; + + if (encrypted) { + git_buf buf = GIT_BUF_INIT; + git_buf_printf(&buf, "https://%s", host); + curl_easy_setopt(handle, CURLOPT_URL, buf.ptr); + git_buf_free(&buf); + } else { + curl_easy_setopt(handle, CURLOPT_URL, host); + } + + curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, st->curl_error); + curl_easy_setopt(handle, CURLOPT_PORT, iport); + curl_easy_setopt(handle, CURLOPT_CONNECT_ONLY, 1); + curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 1); + curl_easy_setopt(handle, CURLOPT_CERTINFO, 1); + /* curl_easy_setopt(handle, CURLOPT_VERBOSE, 1); */ + + st->parent.version = GIT_STREAM_VERSION; + st->parent.encrypted = encrypted; + st->parent.connect = curls_connect; + st->parent.certificate = curls_certificate; + st->parent.read = curls_read; + st->parent.write = curls_write; + st->parent.close = curls_close; + st->parent.free = curls_free; + st->handle = handle; + + *out = (git_stream *) st; + return 0; +} + +#else + +#include "stream.h" + +int git_curl_stream_new(git_stream **out, const char *host, const char *port) +{ + GIT_UNUSED(out); + GIT_UNUSED(host); + GIT_UNUSED(port); + + giterr_set(GITERR_NET, "curl is not supported in this version"); + return -1; +} + + +#endif diff --git a/src/curl_stream.h b/src/curl_stream.h new file mode 100644 index 000000000..168fbe8c4 --- /dev/null +++ b/src/curl_stream.h @@ -0,0 +1,14 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_curl_stream_h__ +#define INCLUDE_curl_stream_h__ + +#include "git2/sys/stream.h" + +extern int git_curl_stream_new(git_stream **out, const char *host, const char *port, bool encrypted); + +#endif diff --git a/src/transports/http.c b/src/transports/http.c index 4aca2755b..8cf011f4c 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -15,6 +15,7 @@ #include "auth_negotiate.h" #include "tls_stream.h" #include "socket_stream.h" +#include "curl_stream.h" git_http_auth_scheme auth_schemes[] = { { GIT_AUTHTYPE_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, git_http_auth_negotiate }, @@ -544,11 +545,15 @@ static int http_connect(http_subtransport *t) t->io = NULL; } +#ifdef GIT_CURL + error = git_curl_stream_new(&t->io, t->connection_data.host, t->connection_data.port, t->connection_data.use_ssl); +#else if (t->connection_data.use_ssl) { error = git_tls_stream_new(&t->io, t->connection_data.host, t->connection_data.port); } else { error = git_socket_stream_new(&t->io, t->connection_data.host, t->connection_data.port); } +#endif if (error < 0) return error; @@ -557,7 +562,7 @@ static int http_connect(http_subtransport *t) error = git_stream_connect(t->io); -#if defined(GIT_OPENSSL) || defined(GIT_SECURE_TRANSPORT) +#if defined(GIT_OPENSSL) || defined(GIT_SECURE_TRANSPORT) || defined(GIT_CURL) if ((!error || error == GIT_ECERTIFICATE) && t->owner->certificate_check_cb != NULL && git_stream_is_encrypted(t->io)) { git_cert *cert;