mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-07 23:19:28 +00:00
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.
This commit is contained in:
parent
cf9d5f7602
commit
8dea1c21f5
219
src/curl_stream.c
Normal file
219
src/curl_stream.c
Normal file
@ -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
|
14
src/curl_stream.h
Normal file
14
src/curl_stream.h
Normal file
@ -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
|
@ -15,6 +15,7 @@
|
|||||||
#include "auth_negotiate.h"
|
#include "auth_negotiate.h"
|
||||||
#include "tls_stream.h"
|
#include "tls_stream.h"
|
||||||
#include "socket_stream.h"
|
#include "socket_stream.h"
|
||||||
|
#include "curl_stream.h"
|
||||||
|
|
||||||
git_http_auth_scheme auth_schemes[] = {
|
git_http_auth_scheme auth_schemes[] = {
|
||||||
{ GIT_AUTHTYPE_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, git_http_auth_negotiate },
|
{ 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;
|
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) {
|
if (t->connection_data.use_ssl) {
|
||||||
error = git_tls_stream_new(&t->io, t->connection_data.host, t->connection_data.port);
|
error = git_tls_stream_new(&t->io, t->connection_data.host, t->connection_data.port);
|
||||||
} else {
|
} else {
|
||||||
error = git_socket_stream_new(&t->io, t->connection_data.host, t->connection_data.port);
|
error = git_socket_stream_new(&t->io, t->connection_data.host, t->connection_data.port);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (error < 0)
|
if (error < 0)
|
||||||
return error;
|
return error;
|
||||||
@ -557,7 +562,7 @@ static int http_connect(http_subtransport *t)
|
|||||||
|
|
||||||
error = git_stream_connect(t->io);
|
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 &&
|
if ((!error || error == GIT_ECERTIFICATE) && t->owner->certificate_check_cb != NULL &&
|
||||||
git_stream_is_encrypted(t->io)) {
|
git_stream_is_encrypted(t->io)) {
|
||||||
git_cert *cert;
|
git_cert *cert;
|
||||||
|
Loading…
Reference in New Issue
Block a user