Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e1f434f

Browse files
committed
Merge pull request libgit2#3183 from libgit2/cmn/curl-stream
Implement a cURL stream
2 parents 9d5efab + 58ca8c7 commit e1f434f

File tree

11 files changed

+475
-23
lines changed

11 files changed

+475
-23
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ support for HTTPS connections insead of OpenSSL.
7272
* The race condition mitigations described in `racy-git.txt` have been
7373
implemented.
7474

75+
* If libcurl is installed, we will use it to connect to HTTP(S)
76+
servers.
77+
7578
### API additions
7679

7780
* The `git_merge_options` gained a `file_flags` member.

CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ OPTION( USE_ICONV "Link with and use iconv library" OFF )
3838
OPTION( USE_SSH "Link with libssh to enable SSH support" ON )
3939
OPTION( USE_GSSAPI "Link with libgssapi for SPNEGO auth" OFF )
4040
OPTION( VALGRIND "Configure build for valgrind" OFF )
41+
OPTION( CURL "User curl for HTTP if available" ON)
4142

4243
IF(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
4344
SET( USE_ICONV ON )
@@ -199,10 +200,20 @@ IF (WIN32 AND WINHTTP)
199200

200201
LINK_LIBRARIES(winhttp rpcrt4 crypt32)
201202
ELSE ()
203+
IF (CURL)
204+
FIND_PACKAGE(CURL)
205+
ENDIF ()
206+
202207
IF (NOT AMIGA AND USE_OPENSSL)
203208
FIND_PACKAGE(OpenSSL)
204209
ENDIF ()
205210

211+
IF (CURL_FOUND)
212+
ADD_DEFINITIONS(-DGIT_CURL)
213+
INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIRS})
214+
LINK_LIBRARIES(${CURL_LIBRARIES})
215+
ENDIF()
216+
206217
FIND_PACKAGE(HTTP_Parser)
207218
IF (HTTP_PARSER_FOUND AND HTTP_PARSER_VERSION_MAJOR EQUAL 2)
208219
INCLUDE_DIRECTORIES(${HTTP_PARSER_INCLUDE_DIRS})

THREADING.md

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,22 +47,30 @@ you.
4747
On Mac OS X
4848
-----------
4949

50-
On OS X, the library makes use of CommonCrypto and SecureTransport for
51-
cryptographic support. These are thread-safe and you do not need to do
52-
anything special.
50+
By default we use libcurl to perform the encryption. The
51+
system-provided libcurl uses SecureTransport, so no special steps are
52+
necessary. If you link against another libcurl (e.g. from homebrew)
53+
refer to the general case.
54+
55+
If the option to use libcurl was deactivated, the library makes use of
56+
CommonCrypto and SecureTransport for cryptographic support. These are
57+
thread-safe and you do not need to do anything special.
5358

5459
Note that libssh2 may still use OpenSSL itself. In that case, the
5560
general case still affects you if you use ssh.
5661

5762
General Case
5863
------------
5964

60-
On the rest of the platforms, libgit2 uses OpenSSL to be able to use
61-
HTTPS as a transport. This library is made to be thread-implementation
62-
agnostic, and the users of the library must set which locking function
63-
it should use. This means that libgit2 cannot know what to set as the
64-
user of libgit2 may use OpenSSL independently and the locking settings
65-
must survive libgit2 shutting down.
65+
By default we use libcurl, which has its own ![recommendations for
66+
thread safety](http://curl.haxx.se/libcurl/c/libcurl-tutorial.html#Multi-threading).
67+
68+
If libcurl was not found or was disabled, libgit2 uses OpenSSL to be
69+
able to use HTTPS as a transport. This library is made to be
70+
thread-implementation agnostic, and the users of the library must set
71+
which locking function it should use. This means that libgit2 cannot
72+
know what to set as the user of libgit2 may use OpenSSL independently
73+
and the locking settings must survive libgit2 shutting down.
6674

6775
libgit2 does provide a last-resort convenience function
6876
`git_openssl_set_locking()` (available in `sys/openssl.h`) to use the

include/git2/sys/stream.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ typedef struct git_stream {
2929
int version;
3030

3131
int encrypted;
32+
int proxy_support;
3233
int (*connect)(struct git_stream *);
3334
int (*certificate)(git_cert **, struct git_stream *);
35+
int (*set_proxy)(struct git_stream *, const char *proxy_url);
3436
ssize_t (*read)(struct git_stream *, void *, size_t);
3537
ssize_t (*write)(struct git_stream *, const char *, size_t, int);
3638
int (*close)(struct git_stream *);

include/git2/types.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,11 @@ typedef int (*git_transport_message_cb)(const char *str, int len, void *payload)
284284
* Type of host certificate structure that is passed to the check callback
285285
*/
286286
typedef enum git_cert_t {
287+
/**
288+
* No information about the certificate is available. This may
289+
* happen when using curl.
290+
*/
291+
GIT_CERT_NONE,
287292
/**
288293
* The `data` argument to the callback will be a pointer to
289294
* the DER-encoded data.
@@ -294,6 +299,13 @@ typedef enum git_cert_t {
294299
* `git_cert_hostkey` structure.
295300
*/
296301
GIT_CERT_HOSTKEY_LIBSSH2,
302+
/**
303+
* The `data` argument to the callback will be a pointer to a
304+
* `git_strarray` with `name:content` strings containing
305+
* information about the certificate. This is used when using
306+
* curl.
307+
*/
308+
GIT_CERT_STRARRAY,
297309
} git_cert_t;
298310

299311
/**

src/curl_stream.c

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
/*
2+
* Copyright (C) the libgit2 contributors. All rights reserved.
3+
*
4+
* This file is part of libgit2, distributed under the GNU GPL v2 with
5+
* a Linking Exception. For full terms see the included COPYING file.
6+
*/
7+
8+
#ifdef GIT_CURL
9+
10+
#include <curl/curl.h>
11+
12+
#include "stream.h"
13+
#include "git2/transport.h"
14+
#include "buffer.h"
15+
#include "vector.h"
16+
17+
typedef struct {
18+
git_stream parent;
19+
CURL *handle;
20+
curl_socket_t socket;
21+
char curl_error[CURL_ERROR_SIZE + 1];
22+
git_cert_x509 cert_info;
23+
git_strarray cert_info_strings;
24+
} curl_stream;
25+
26+
static int seterr_curl(curl_stream *s)
27+
{
28+
giterr_set(GITERR_NET, "curl error: %s\n", s->curl_error);
29+
return -1;
30+
}
31+
32+
static int curls_connect(git_stream *stream)
33+
{
34+
curl_stream *s = (curl_stream *) stream;
35+
long sockextr;
36+
int failed_cert = 0;
37+
CURLcode res;
38+
res = curl_easy_perform(s->handle);
39+
40+
if (res != CURLE_OK && res != CURLE_PEER_FAILED_VERIFICATION)
41+
return seterr_curl(s);
42+
if (res == CURLE_PEER_FAILED_VERIFICATION)
43+
failed_cert = 1;
44+
45+
if ((res = curl_easy_getinfo(s->handle, CURLINFO_LASTSOCKET, &sockextr)) != CURLE_OK)
46+
return seterr_curl(s);
47+
48+
s->socket = sockextr;
49+
50+
if (s->parent.encrypted && failed_cert)
51+
return GIT_ECERTIFICATE;
52+
53+
return 0;
54+
}
55+
56+
static int curls_certificate(git_cert **out, git_stream *stream)
57+
{
58+
int error;
59+
CURLcode res;
60+
struct curl_slist *slist;
61+
struct curl_certinfo *certinfo;
62+
git_vector strings = GIT_VECTOR_INIT;
63+
curl_stream *s = (curl_stream *) stream;
64+
65+
if ((res = curl_easy_getinfo(s->handle, CURLINFO_CERTINFO, &certinfo)) != CURLE_OK)
66+
return seterr_curl(s);
67+
68+
/* No information is available, can happen with SecureTransport */
69+
if (certinfo->num_of_certs == 0) {
70+
s->cert_info.cert_type = GIT_CERT_NONE;
71+
s->cert_info.data = NULL;
72+
s->cert_info.len = 0;
73+
return 0;
74+
}
75+
76+
if ((error = git_vector_init(&strings, 8, NULL)) < 0)
77+
return error;
78+
79+
for (slist = certinfo->certinfo[0]; slist; slist = slist->next) {
80+
char *str = git__strdup(slist->data);
81+
GITERR_CHECK_ALLOC(str);
82+
}
83+
84+
/* Copy the contents of the vector into a strarray so we can expose them */
85+
s->cert_info_strings.strings = (char **) strings.contents;
86+
s->cert_info_strings.count = strings.length;
87+
88+
s->cert_info.cert_type = GIT_CERT_STRARRAY;
89+
s->cert_info.data = &s->cert_info_strings;
90+
s->cert_info.len = strings.length;
91+
92+
*out = (git_cert *) &s->cert_info;
93+
94+
return 0;
95+
}
96+
97+
static int curls_set_proxy(git_stream *stream, const char *proxy_url)
98+
{
99+
CURLcode res;
100+
curl_stream *s = (curl_stream *) stream;
101+
102+
if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXY, proxy_url)) != CURLE_OK)
103+
return seterr_curl(s);
104+
105+
return 0;
106+
}
107+
108+
static int wait_for(curl_socket_t fd, bool reading)
109+
{
110+
int ret;
111+
fd_set infd, outfd, errfd;
112+
113+
FD_ZERO(&infd);
114+
FD_ZERO(&outfd);
115+
FD_ZERO(&errfd);
116+
117+
FD_SET(fd, &errfd);
118+
if (reading)
119+
FD_SET(fd, &infd);
120+
else
121+
FD_SET(fd, &outfd);
122+
123+
if ((ret = select(fd + 1, &infd, &outfd, &errfd, NULL)) < 0) {
124+
giterr_set(GITERR_OS, "error in select");
125+
return -1;
126+
}
127+
128+
return 0;
129+
}
130+
131+
static ssize_t curls_write(git_stream *stream, const char *data, size_t len, int flags)
132+
{
133+
int error;
134+
size_t off = 0, sent;
135+
CURLcode res;
136+
curl_stream *s = (curl_stream *) stream;
137+
138+
GIT_UNUSED(flags);
139+
140+
do {
141+
if ((error = wait_for(s->socket, false)) < 0)
142+
return error;
143+
144+
res = curl_easy_send(s->handle, data + off, len - off, &sent);
145+
if (res == CURLE_OK)
146+
off += sent;
147+
} while ((res == CURLE_OK || res == CURLE_AGAIN) && off < len);
148+
149+
if (res != CURLE_OK)
150+
return seterr_curl(s);
151+
152+
return len;
153+
}
154+
155+
static ssize_t curls_read(git_stream *stream, void *data, size_t len)
156+
{
157+
int error;
158+
size_t read;
159+
CURLcode res;
160+
curl_stream *s = (curl_stream *) stream;
161+
162+
do {
163+
if ((error = wait_for(s->socket, true)) < 0)
164+
return error;
165+
166+
res = curl_easy_recv(s->handle, data, len, &read);
167+
} while (res == CURLE_AGAIN);
168+
169+
if (res != CURLE_OK)
170+
return seterr_curl(s);
171+
172+
return read;
173+
}
174+
175+
static int curls_close(git_stream *stream)
176+
{
177+
curl_stream *s = (curl_stream *) stream;
178+
179+
if (!s->handle)
180+
return 0;
181+
182+
curl_easy_cleanup(s->handle);
183+
s->handle = NULL;
184+
s->socket = 0;
185+
186+
return 0;
187+
}
188+
189+
static void curls_free(git_stream *stream)
190+
{
191+
curl_stream *s = (curl_stream *) stream;
192+
193+
curls_close(stream);
194+
git_strarray_free(&s->cert_info_strings);
195+
git__free(s);
196+
}
197+
198+
int git_curl_stream_new(git_stream **out, const char *host, const char *port)
199+
{
200+
curl_stream *st;
201+
CURL *handle;
202+
int iport = 0, error;
203+
204+
st = git__calloc(1, sizeof(curl_stream));
205+
GITERR_CHECK_ALLOC(st);
206+
207+
handle = curl_easy_init();
208+
if (handle == NULL) {
209+
giterr_set(GITERR_NET, "failed to create curl handle");
210+
return -1;
211+
}
212+
213+
if ((error = git__strtol32(&iport, port, NULL, 10)) < 0)
214+
return error;
215+
216+
curl_easy_setopt(handle, CURLOPT_URL, host);
217+
curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, st->curl_error);
218+
curl_easy_setopt(handle, CURLOPT_PORT, iport);
219+
curl_easy_setopt(handle, CURLOPT_CONNECT_ONLY, 1);
220+
curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 1);
221+
curl_easy_setopt(handle, CURLOPT_CERTINFO, 1);
222+
curl_easy_setopt(handle, CURLOPT_HTTPPROXYTUNNEL, 1);
223+
224+
/* curl_easy_setopt(handle, CURLOPT_VERBOSE, 1); */
225+
226+
st->parent.version = GIT_STREAM_VERSION;
227+
st->parent.encrypted = 0; /* we don't encrypt ourselves */
228+
st->parent.proxy_support = 1;
229+
st->parent.connect = curls_connect;
230+
st->parent.certificate = curls_certificate;
231+
st->parent.set_proxy = curls_set_proxy;
232+
st->parent.read = curls_read;
233+
st->parent.write = curls_write;
234+
st->parent.close = curls_close;
235+
st->parent.free = curls_free;
236+
st->handle = handle;
237+
238+
*out = (git_stream *) st;
239+
return 0;
240+
}
241+
242+
#else
243+
244+
#include "stream.h"
245+
246+
int git_curl_stream_new(git_stream **out, const char *host, const char *port)
247+
{
248+
GIT_UNUSED(out);
249+
GIT_UNUSED(host);
250+
GIT_UNUSED(port);
251+
252+
giterr_set(GITERR_NET, "curl is not supported in this version");
253+
return -1;
254+
}
255+
256+
257+
#endif

src/curl_stream.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright (C) the libgit2 contributors. All rights reserved.
3+
*
4+
* This file is part of libgit2, distributed under the GNU GPL v2 with
5+
* a Linking Exception. For full terms see the included COPYING file.
6+
*/
7+
#ifndef INCLUDE_curl_stream_h__
8+
#define INCLUDE_curl_stream_h__
9+
10+
#include "git2/sys/stream.h"
11+
12+
extern int git_curl_stream_new(git_stream **out, const char *host, const char *port);
13+
14+
#endif

0 commit comments

Comments
 (0)