|
| 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 |
0 commit comments