Commit 8dea1c21f5edcf9ff44282134e416e54e6eaf9be

Carlos Martín Nieto 2015-06-05T11:02:11

Implement a curl stream cURL has a mode in which it acts a lot like our streams, providing send and recv functions and taking care of the TLS and proxy setup for us. Implement a new stream which uses libcurl instead of raw sockets or the TLS libraries directly. This version does not support reporting certificates or proxies yet.

diff --git a/src/curl_stream.c b/src/curl_stream.c
new file mode 100644
index 0000000..fb7a613
--- /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 <curl/curl.h>
+
+#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 0000000..168fbe8
--- /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 4aca275..8cf011f 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;