summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2018-11-28 20:31:30 +0000
committerGitHub <noreply@github.com>2018-11-28 20:31:30 +0000
commita904fc6d12ffc20aa1a238a78000921494222e89 (patch)
treec978c4a381618ff7d6c6cd665ed7bb703973d0f8 /src
parentdcd00638a594b463b247530769f4883cd7d983b1 (diff)
parent30ac46aa469b06cd47efa9625c2bf4862f8494b7 (diff)
downloadlibgit2-a904fc6d12ffc20aa1a238a78000921494222e89.tar.gz
Merge pull request #4870 from libgit2/ethomson/proxy
Add builtin proxy support for the http transport
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt11
-rw-r--r--src/features.h.in1
-rw-r--r--src/global.c4
-rw-r--r--src/streams/curl.c385
-rw-r--r--src/streams/curl.h17
-rw-r--r--src/streams/mbedtls.c80
-rw-r--r--src/streams/mbedtls.h7
-rw-r--r--src/streams/openssl.c79
-rw-r--r--src/streams/openssl.h7
-rw-r--r--src/streams/registry.c116
-rw-r--r--src/streams/registry.h19
-rw-r--r--src/streams/socket.c34
-rw-r--r--src/streams/stransport.c63
-rw-r--r--src/streams/stransport.h5
-rw-r--r--src/streams/tls.c68
-rw-r--r--src/streams/tls.h16
-rw-r--r--src/transports/auth.c7
-rw-r--r--src/transports/auth.h2
-rw-r--r--src/transports/auth_negotiate.c3
-rw-r--r--src/transports/http.c774
-rw-r--r--src/transports/http.h2
-rw-r--r--src/transports/winhttp.c2
22 files changed, 956 insertions, 746 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 8ba3aa590..5ad8b1447 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -125,17 +125,6 @@ IF (WIN32 AND WINHTTP)
LIST(APPEND LIBGIT2_LIBS "rpcrt4" "crypt32" "ole32")
LIST(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32")
-ELSE ()
- IF (CURL)
- FIND_PKGLIBRARIES(CURL libcurl)
- ENDIF ()
- IF (CURL_FOUND)
- SET(GIT_CURL 1)
- LIST(APPEND LIBGIT2_SYSTEM_INCLUDES ${CURL_INCLUDE_DIRS})
- LIST(APPEND LIBGIT2_LIBS ${CURL_LIBRARIES})
- LIST(APPEND LIBGIT2_PC_LIBS ${CURL_LDFLAGS})
- ENDIF()
- ADD_FEATURE_INFO(cURL GIT_CURL "cURL for HTTP proxy support")
ENDIF()
IF (USE_HTTPS)
diff --git a/src/features.h.in b/src/features.h.in
index f414c5843..694a61c02 100644
--- a/src/features.h.in
+++ b/src/features.h.in
@@ -22,7 +22,6 @@
#cmakedefine GIT_GSSAPI 1
#cmakedefine GIT_WINHTTP 1
-#cmakedefine GIT_CURL 1
#cmakedefine GIT_HTTPS 1
#cmakedefine GIT_OPENSSL 1
diff --git a/src/global.c b/src/global.c
index d33772e91..86a35a2ff 100644
--- a/src/global.c
+++ b/src/global.c
@@ -12,7 +12,7 @@
#include "sysdir.h"
#include "filter.h"
#include "merge_driver.h"
-#include "streams/curl.h"
+#include "streams/registry.h"
#include "streams/mbedtls.h"
#include "streams/openssl.h"
#include "thread-utils.h"
@@ -67,8 +67,8 @@ static int init_common(void)
(ret = git_filter_global_init()) == 0 &&
(ret = git_merge_driver_global_init()) == 0 &&
(ret = git_transport_ssh_global_init()) == 0 &&
+ (ret = git_stream_registry_global_init()) == 0 &&
(ret = git_openssl_stream_global_init()) == 0 &&
- (ret = git_curl_stream_global_init()) == 0 &&
(ret = git_mbedtls_stream_global_init()) == 0)
ret = git_mwindow_global_init();
diff --git a/src/streams/curl.c b/src/streams/curl.c
deleted file mode 100644
index 3c0af3b04..000000000
--- a/src/streams/curl.c
+++ /dev/null
@@ -1,385 +0,0 @@
-/*
- * 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.
- */
-
-#include "streams/curl.h"
-
-#ifdef GIT_CURL
-
-#include <curl/curl.h>
-
-#include "stream.h"
-#include "git2/transport.h"
-#include "buffer.h"
-#include "global.h"
-#include "vector.h"
-#include "proxy.h"
-
-/* This is for backwards compatibility with curl<7.45.0. */
-#ifndef CURLINFO_ACTIVESOCKET
-# define CURLINFO_ACTIVESOCKET CURLINFO_LASTSOCKET
-# define GIT_CURL_BADSOCKET -1
-# define git_activesocket_t long
-#else
-# define GIT_CURL_BADSOCKET CURL_SOCKET_BAD
-# define git_activesocket_t curl_socket_t
-#endif
-
-typedef struct {
- git_stream parent;
- CURL *handle;
- curl_socket_t socket;
- char curl_error[CURL_ERROR_SIZE + 1];
- git_cert_x509 cert_info;
- git_strarray cert_info_strings;
- git_proxy_options proxy;
- git_cred *proxy_cred;
-} curl_stream;
-
-int git_curl_stream_global_init(void)
-{
- if (curl_global_init(CURL_GLOBAL_ALL) != 0) {
- giterr_set(GITERR_NET, "could not initialize curl");
- return -1;
- }
-
- /* `curl_global_cleanup` is provided by libcurl */
- git__on_shutdown(curl_global_cleanup);
- return 0;
-}
-
-static int seterr_curl(curl_stream *s)
-{
- giterr_set(GITERR_NET, "curl error: %s\n", s->curl_error);
- return -1;
-}
-
-GIT_INLINE(int) error_no_credentials(void)
-{
- giterr_set(GITERR_NET, "proxy authentication required, but no callback provided");
- return GIT_EAUTH;
-}
-
-static int apply_proxy_creds(curl_stream *s)
-{
- CURLcode res;
- git_cred_userpass_plaintext *userpass;
-
- if (!s->proxy_cred)
- return GIT_ENOTFOUND;
-
- userpass = (git_cred_userpass_plaintext *) s->proxy_cred;
- if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXYUSERNAME, userpass->username)) != CURLE_OK)
- return seterr_curl(s);
- if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXYPASSWORD, userpass->password)) != CURLE_OK)
- return seterr_curl(s);
-
- return 0;
-}
-
-static int ask_and_apply_proxy_creds(curl_stream *s)
-{
- int error;
- git_proxy_options *opts = &s->proxy;
-
- if (!opts->credentials)
- return error_no_credentials();
-
- /* TODO: see if PROXYAUTH_AVAIL helps us here */
- git_cred_free(s->proxy_cred);
- s->proxy_cred = NULL;
- giterr_clear();
- error = opts->credentials(&s->proxy_cred, opts->url, NULL, GIT_CREDTYPE_USERPASS_PLAINTEXT, opts->payload);
- if (error == GIT_PASSTHROUGH)
- return error_no_credentials();
- if (error < 0) {
- if (!giterr_last())
- giterr_set(GITERR_NET, "proxy authentication was aborted by the user");
- return error;
- }
-
- if (s->proxy_cred->credtype != GIT_CREDTYPE_USERPASS_PLAINTEXT) {
- giterr_set(GITERR_NET, "credentials callback returned invalid credential type");
- return -1;
- }
-
- return apply_proxy_creds(s);
-}
-
-static int curls_connect(git_stream *stream)
-{
- curl_stream *s = (curl_stream *) stream;
- git_activesocket_t sockextr;
- long connect_last = 0;
- int failed_cert = 0, error;
- bool retry_connect;
- CURLcode res;
-
- /* Apply any credentials we've already established */
- error = apply_proxy_creds(s);
- if (error < 0 && error != GIT_ENOTFOUND)
- return seterr_curl(s);
-
- do {
- retry_connect = 0;
- res = curl_easy_perform(s->handle);
-
- curl_easy_getinfo(s->handle, CURLINFO_HTTP_CONNECTCODE, &connect_last);
-
- /* HTTP 407 Proxy Authentication Required */
- if (connect_last == 407) {
- if ((error = ask_and_apply_proxy_creds(s)) < 0)
- return error;
-
- retry_connect = true;
- }
- } while (retry_connect);
-
- 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_ACTIVESOCKET, &sockextr)) != CURLE_OK) {
- return seterr_curl(s);
- }
-
- if (sockextr == GIT_CURL_BADSOCKET) {
- giterr_set(GITERR_NET, "curl socket is no longer valid");
- return -1;
- }
-
- s->socket = sockextr;
-
- if (s->parent.encrypted && failed_cert)
- return GIT_ECERTIFICATE;
-
- return 0;
-}
-
-static int curls_certificate(git_cert **out, git_stream *stream)
-{
- int error;
- CURLcode res;
- struct curl_slist *slist;
- struct curl_certinfo *certinfo;
- git_vector strings = GIT_VECTOR_INIT;
- curl_stream *s = (curl_stream *) stream;
-
- if ((res = curl_easy_getinfo(s->handle, CURLINFO_CERTINFO, &certinfo)) != CURLE_OK)
- return seterr_curl(s);
-
- /* No information is available, can happen with SecureTransport */
- if (certinfo->num_of_certs == 0) {
- s->cert_info.parent.cert_type = GIT_CERT_NONE;
- s->cert_info.data = NULL;
- s->cert_info.len = 0;
- return 0;
- }
-
- if ((error = git_vector_init(&strings, 8, NULL)) < 0)
- return error;
-
- for (slist = certinfo->certinfo[0]; slist; slist = slist->next) {
- char *str = git__strdup(slist->data);
- GITERR_CHECK_ALLOC(str);
- git_vector_insert(&strings, str);
- }
-
- /* Copy the contents of the vector into a strarray so we can expose them */
- s->cert_info_strings.strings = (char **) strings.contents;
- s->cert_info_strings.count = strings.length;
-
- s->cert_info.parent.cert_type = GIT_CERT_STRARRAY;
- s->cert_info.data = &s->cert_info_strings;
- s->cert_info.len = strings.length;
-
- *out = &s->cert_info.parent;
-
- return 0;
-}
-
-static int curls_set_proxy(git_stream *stream, const git_proxy_options *proxy_opts)
-{
- int error;
- CURLcode res;
- curl_stream *s = (curl_stream *) stream;
-
- git_proxy_options_clear(&s->proxy);
- if ((error = git_proxy_options_dup(&s->proxy, proxy_opts)) < 0)
- return error;
-
- if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXY, s->proxy.url)) != CURLE_OK)
- return seterr_curl(s);
-
- if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY)) != CURLE_OK)
- return seterr_curl(s);
-
- 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);
-
- assert(fd >= 0);
- 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_strarray_free(&s->cert_info_strings);
- git_proxy_options_clear(&s->proxy);
- git_cred_free(s->proxy_cred);
- git__free(s);
-}
-
-int git_curl_stream_new(git_stream **out, const char *host, const char *port)
-{
- 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");
- git__free(st);
- return -1;
- }
-
- if ((error = git__strntol32(&iport, port, strlen(port), NULL, 10)) < 0) {
- git__free(st);
- return error;
- }
-
- 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_HTTPPROXYTUNNEL, 1);
- curl_easy_setopt(handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
-
- /* curl_easy_setopt(handle, CURLOPT_VERBOSE, 1); */
-
- st->parent.version = GIT_STREAM_VERSION;
- st->parent.encrypted = 0; /* we don't encrypt ourselves */
- st->parent.proxy_support = 1;
- st->parent.connect = curls_connect;
- st->parent.certificate = curls_certificate;
- st->parent.set_proxy = curls_set_proxy;
- 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_global_init(void)
-{
- return 0;
-}
-
-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/streams/curl.h b/src/streams/curl.h
deleted file mode 100644
index 511cd894a..000000000
--- a/src/streams/curl.h
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * 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_streams_curl_h__
-#define INCLUDE_streams_curl_h__
-
-#include "common.h"
-
-#include "git2/sys/stream.h"
-
-extern int git_curl_stream_global_init(void);
-extern int git_curl_stream_new(git_stream **out, const char *host, const char *port);
-
-#endif
diff --git a/src/streams/mbedtls.c b/src/streams/mbedtls.c
index bd7bd3126..d22f77069 100644
--- a/src/streams/mbedtls.c
+++ b/src/streams/mbedtls.c
@@ -18,10 +18,6 @@
#include "git2/transport.h"
#include "util.h"
-#ifdef GIT_CURL
-# include "streams/curl.h"
-#endif
-
#ifndef GIT_DEFAULT_CERT_LOCATION
#define GIT_DEFAULT_CERT_LOCATION NULL
#endif
@@ -242,6 +238,7 @@ static int verify_server_cert(mbedtls_ssl_context *ssl)
typedef struct {
git_stream parent;
git_stream *io;
+ int owned;
bool connected;
char *host;
mbedtls_ssl_context *ssl;
@@ -254,7 +251,7 @@ int mbedtls_connect(git_stream *stream)
int ret;
mbedtls_stream *st = (mbedtls_stream *) stream;
- if ((ret = git_stream_connect(st->io)) < 0)
+ if (st->owned && (ret = git_stream_connect(st->io)) < 0)
return ret;
st->connected = true;
@@ -345,37 +342,37 @@ int mbedtls_stream_close(git_stream *stream)
st->connected = false;
- return git_stream_close(st->io);
+ return st->owned ? git_stream_close(st->io) : 0;
}
void mbedtls_stream_free(git_stream *stream)
{
mbedtls_stream *st = (mbedtls_stream *) stream;
+ if (st->owned)
+ git_stream_free(st->io);
+
git__free(st->host);
git__free(st->cert_info.data);
- git_stream_free(st->io);
mbedtls_ssl_free(st->ssl);
git__free(st->ssl);
git__free(st);
}
-int git_mbedtls_stream_new(git_stream **out, const char *host, const char *port)
+static int mbedtls_stream_wrap(
+ git_stream **out,
+ git_stream *in,
+ const char *host,
+ int owned)
{
- int error;
mbedtls_stream *st;
+ int error;
st = git__calloc(1, sizeof(mbedtls_stream));
GITERR_CHECK_ALLOC(st);
-#ifdef GIT_CURL
- error = git_curl_stream_new(&st->io, host, port);
-#else
- error = git_socket_stream_new(&st->io, host, port);
-#endif
-
- if (error < 0)
- goto out_err;
+ st->io = in;
+ st->owned = owned;
st->ssl = git__malloc(sizeof(mbedtls_ssl_context));
GITERR_CHECK_ALLOC(st->ssl);
@@ -405,12 +402,42 @@ int git_mbedtls_stream_new(git_stream **out, const char *host, const char *port)
out_err:
mbedtls_ssl_free(st->ssl);
+ git_stream_close(st->io);
git_stream_free(st->io);
git__free(st);
return error;
}
+int git_mbedtls_stream_wrap(
+ git_stream **out,
+ git_stream *in,
+ const char *host)
+{
+ return mbedtls_stream_wrap(out, in, host, 0);
+}
+
+int git_mbedtls_stream_new(
+ git_stream **out,
+ const char *host,
+ const char *port)
+{
+ git_stream *stream;
+ int error;
+
+ assert(out && host && port);
+
+ if ((error = git_socket_stream_new(&stream, host, port)) < 0)
+ return error;
+
+ if ((error = mbedtls_stream_wrap(out, stream, host, 1)) < 0) {
+ git_stream_close(stream);
+ git_stream_free(stream);
+ }
+
+ return error;
+}
+
int git_mbedtls__set_cert_location(const char *path, int is_dir)
{
int ret = 0;
@@ -453,23 +480,4 @@ int git_mbedtls_stream_global_init(void)
return 0;
}
-int git_mbedtls_stream_new(git_stream **out, const char *host, const char *port)
-{
- GIT_UNUSED(out);
- GIT_UNUSED(host);
- GIT_UNUSED(port);
-
- giterr_set(GITERR_SSL, "mbedTLS is not supported in this version");
- return -1;
-}
-
-int git_mbedtls__set_cert_location(const char *path, int is_dir)
-{
- GIT_UNUSED(path);
- GIT_UNUSED(is_dir);
-
- giterr_set(GITERR_SSL, "mbedTLS is not supported in this version");
- return -1;
-}
-
#endif
diff --git a/src/streams/mbedtls.h b/src/streams/mbedtls.h
index 7283698ff..7de94b9fb 100644
--- a/src/streams/mbedtls.h
+++ b/src/streams/mbedtls.h
@@ -13,8 +13,11 @@
extern int git_mbedtls_stream_global_init(void);
-extern int git_mbedtls_stream_new(git_stream **out, const char *host, const char *port);
-
+#ifdef GIT_MBEDTLS
extern int git_mbedtls__set_cert_location(const char *path, int is_dir);
+extern int git_mbedtls_stream_new(git_stream **out, const char *host, const char *port);
+extern int git_mbedtls_stream_wrap(git_stream **out, git_stream *in, const char *host);
+#endif
+
#endif
diff --git a/src/streams/openssl.c b/src/streams/openssl.c
index a68f9a9cc..bc129217d 100644
--- a/src/streams/openssl.c
+++ b/src/streams/openssl.c
@@ -19,10 +19,6 @@
#include "git2/transport.h"
#include "git2/sys/openssl.h"
-#ifdef GIT_CURL
-# include "streams/curl.h"
-#endif
-
#ifndef GIT_WIN32
# include <sys/types.h>
# include <sys/socket.h>
@@ -569,6 +565,7 @@ cleanup:
typedef struct {
git_stream parent;
git_stream *io;
+ int owned;
bool connected;
char *host;
SSL *ssl;
@@ -583,7 +580,7 @@ int openssl_connect(git_stream *stream)
BIO *bio;
openssl_stream *st = (openssl_stream *) stream;
- if ((ret = git_stream_connect(st->io)) < 0)
+ if (st->owned && (ret = git_stream_connect(st->io)) < 0)
return ret;
bio = BIO_new(git_stream_bio_method);
@@ -682,43 +679,43 @@ int openssl_close(git_stream *stream)
st->connected = false;
- return git_stream_close(st->io);
+ return st->owned ? git_stream_close(st->io) : 0;
}
void openssl_free(git_stream *stream)
{
openssl_stream *st = (openssl_stream *) stream;
+ if (st->owned)
+ git_stream_free(st->io);
+
SSL_free(st->ssl);
git__free(st->host);
git__free(st->cert_info.data);
- git_stream_free(st->io);
git__free(st);
}
-int git_openssl_stream_new(git_stream **out, const char *host, const char *port)
+static int openssl_stream_wrap(
+ git_stream **out,
+ git_stream *in,
+ const char *host,
+ int owned)
{
- int error;
openssl_stream *st;
+ assert(out && in && host);
+
st = git__calloc(1, sizeof(openssl_stream));
GITERR_CHECK_ALLOC(st);
- st->io = NULL;
-#ifdef GIT_CURL
- error = git_curl_stream_new(&st->io, host, port);
-#else
- error = git_socket_stream_new(&st->io, host, port);
-#endif
-
- if (error < 0)
- goto out_err;
+ st->io = in;
+ st->owned = owned;
st->ssl = SSL_new(git__ssl_ctx);
if (st->ssl == NULL) {
giterr_set(GITERR_SSL, "failed to create ssl object");
- error = -1;
- goto out_err;
+ git__free(st);
+ return -1;
}
st->host = git__strdup(host);
@@ -737,10 +734,27 @@ int git_openssl_stream_new(git_stream **out, const char *host, const char *port)
*out = (git_stream *) st;
return 0;
+}
-out_err:
- git_stream_free(st->io);
- git__free(st);
+int git_openssl_stream_wrap(git_stream **out, git_stream *in, const char *host)
+{
+ return openssl_stream_wrap(out, in, host, 0);
+}
+
+int git_openssl_stream_new(git_stream **out, const char *host, const char *port)
+{
+ git_stream *stream = NULL;
+ int error;
+
+ assert(out && host && port);
+
+ if ((error = git_socket_stream_new(&stream, host, port)) < 0)
+ return error;
+
+ if ((error = openssl_stream_wrap(out, stream, host, 1)) < 0) {
+ git_stream_close(stream);
+ git_stream_free(stream);
+ }
return error;
}
@@ -775,23 +789,4 @@ int git_openssl_set_locking(void)
return -1;
}
-int git_openssl_stream_new(git_stream **out, const char *host, const char *port)
-{
- GIT_UNUSED(out);
- GIT_UNUSED(host);
- GIT_UNUSED(port);
-
- giterr_set(GITERR_SSL, "openssl is not supported in this version");
- return -1;
-}
-
-int git_openssl__set_cert_location(const char *file, const char *path)
-{
- GIT_UNUSED(file);
- GIT_UNUSED(path);
-
- giterr_set(GITERR_SSL, "openssl is not supported in this version");
- return -1;
-}
-
#endif
diff --git a/src/streams/openssl.h b/src/streams/openssl.h
index 496d7efbf..826d1efbc 100644
--- a/src/streams/openssl.h
+++ b/src/streams/openssl.h
@@ -13,8 +13,11 @@
extern int git_openssl_stream_global_init(void);
-extern int git_openssl_stream_new(git_stream **out, const char *host, const char *port);
-
+#ifdef GIT_OPENSSL
extern int git_openssl__set_cert_location(const char *file, const char *path);
+extern int git_openssl_stream_new(git_stream **out, const char *host, const char *port);
+extern int git_openssl_stream_wrap(git_stream **out, git_stream *in, const char *host);
+#endif
+
#endif
diff --git a/src/streams/registry.c b/src/streams/registry.c
new file mode 100644
index 000000000..02fd3491b
--- /dev/null
+++ b/src/streams/registry.c
@@ -0,0 +1,116 @@
+/*
+ * 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.
+ */
+
+#include "git2/errors.h"
+
+#include "common.h"
+#include "global.h"
+#include "streams/tls.h"
+#include "streams/mbedtls.h"
+#include "streams/openssl.h"
+#include "streams/stransport.h"
+
+struct stream_registry {
+ git_rwlock lock;
+ git_stream_registration callbacks;
+ git_stream_registration tls_callbacks;
+};
+
+static struct stream_registry stream_registry;
+
+static void shutdown_stream_registry(void)
+{
+ git_rwlock_free(&stream_registry.lock);
+}
+
+int git_stream_registry_global_init(void)
+{
+ if (git_rwlock_init(&stream_registry.lock) < 0)
+ return -1;
+
+ git__on_shutdown(shutdown_stream_registry);
+ return 0;
+}
+
+GIT_INLINE(void) stream_registration_cpy(
+ git_stream_registration *target,
+ git_stream_registration *src)
+{
+ if (src)
+ memcpy(target, src, sizeof(git_stream_registration));
+ else
+ memset(target, 0, sizeof(git_stream_registration));
+}
+
+int git_stream_registry_lookup(git_stream_registration *out, git_stream_t type)
+{
+ git_stream_registration *target;
+ int error = GIT_ENOTFOUND;
+
+ assert(out);
+
+ switch(type) {
+ case GIT_STREAM_STANDARD:
+ target = &stream_registry.callbacks;
+ break;
+ case GIT_STREAM_TLS:
+ target = &stream_registry.tls_callbacks;
+ break;
+ default:
+ assert(0);
+ return -1;
+ }
+
+ if (git_rwlock_rdlock(&stream_registry.lock) < 0) {
+ giterr_set(GITERR_OS, "failed to lock stream registry");
+ return -1;
+ }
+
+ if (target->init) {
+ stream_registration_cpy(out, target);
+ error = 0;
+ }
+
+ git_rwlock_rdunlock(&stream_registry.lock);
+ return error;
+}
+
+int git_stream_register(git_stream_t type, git_stream_registration *registration)
+{
+ assert(!registration || registration->init);
+
+ GITERR_CHECK_VERSION(registration, GIT_STREAM_VERSION, "stream_registration");
+
+ if (git_rwlock_wrlock(&stream_registry.lock) < 0) {
+ giterr_set(GITERR_OS, "failed to lock stream registry");
+ return -1;
+ }
+
+ if ((type & GIT_STREAM_STANDARD) == GIT_STREAM_STANDARD)
+ stream_registration_cpy(&stream_registry.callbacks, registration);
+
+ if ((type & GIT_STREAM_TLS) == GIT_STREAM_TLS)
+ stream_registration_cpy(&stream_registry.tls_callbacks, registration);
+
+ git_rwlock_wrunlock(&stream_registry.lock);
+ return 0;
+}
+
+int git_stream_register_tls(git_stream_cb ctor)
+{
+ git_stream_registration registration = {0};
+
+ if (ctor) {
+ registration.version = GIT_STREAM_VERSION;
+ registration.init = ctor;
+ registration.wrap = NULL;
+
+ return git_stream_register(GIT_STREAM_TLS, &registration);
+ } else {
+ return git_stream_register(GIT_STREAM_TLS, NULL);
+ }
+}
diff --git a/src/streams/registry.h b/src/streams/registry.h
new file mode 100644
index 000000000..adc2b8bdf
--- /dev/null
+++ b/src/streams/registry.h
@@ -0,0 +1,19 @@
+/*
+ * 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_streams_registry_h__
+#define INCLUDE_streams_registry_h__
+
+#include "common.h"
+#include "git2/sys/stream.h"
+
+/** Configure stream registry. */
+int git_stream_registry_global_init(void);
+
+/** Lookup a stream registration. */
+extern int git_stream_registry_lookup(git_stream_registration *out, git_stream_t type);
+
+#endif
diff --git a/src/streams/socket.c b/src/streams/socket.c
index 0c6073b66..732b45940 100644
--- a/src/streams/socket.c
+++ b/src/streams/socket.c
@@ -9,6 +9,7 @@
#include "posix.h"
#include "netops.h"
+#include "registry.h"
#include "stream.h"
#ifndef _WIN32
@@ -180,11 +181,14 @@ void socket_free(git_stream *stream)
git__free(st);
}
-int git_socket_stream_new(git_stream **out, const char *host, const char *port)
+static int default_socket_stream_new(
+ git_stream **out,
+ const char *host,
+ const char *port)
{
git_socket_stream *st;
- assert(out && host);
+ assert(out && host && port);
st = git__calloc(1, sizeof(git_socket_stream));
GITERR_CHECK_ALLOC(st);
@@ -208,3 +212,29 @@ int git_socket_stream_new(git_stream **out, const char *host, const char *port)
*out = (git_stream *) st;
return 0;
}
+
+int git_socket_stream_new(
+ git_stream **out,
+ const char *host,
+ const char *port)
+{
+ int (*init)(git_stream **, const char *, const char *) = NULL;
+ git_stream_registration custom = {0};
+ int error;
+
+ assert(out && host && port);
+
+ if ((error = git_stream_registry_lookup(&custom, GIT_STREAM_STANDARD)) == 0)
+ init = custom.init;
+ else if (error == GIT_ENOTFOUND)
+ init = default_socket_stream_new;
+ else
+ return error;
+
+ if (!init) {
+ giterr_set(GITERR_NET, "there is no socket stream available");
+ return -1;
+ }
+
+ return init(out, host, port);
+}
diff --git a/src/streams/stransport.c b/src/streams/stransport.c
index 15b305745..6626e0f68 100644
--- a/src/streams/stransport.c
+++ b/src/streams/stransport.c
@@ -16,7 +16,6 @@
#include "git2/transport.h"
#include "streams/socket.h"
-#include "streams/curl.h"
static int stransport_error(OSStatus ret)
{
@@ -34,8 +33,8 @@ static int stransport_error(OSStatus ret)
giterr_set(GITERR_NET, "SecureTransport error: %s", CFStringGetCStringPtr(message, kCFStringEncodingUTF8));
CFRelease(message);
#else
- giterr_set(GITERR_NET, "SecureTransport error: OSStatus %d", (unsigned int)ret);
- GIT_UNUSED(message);
+ giterr_set(GITERR_NET, "SecureTransport error: OSStatus %d", (unsigned int)ret);
+ GIT_UNUSED(message);
#endif
return -1;
@@ -44,6 +43,7 @@ static int stransport_error(OSStatus ret)
typedef struct {
git_stream parent;
git_stream *io;
+ int owned;
SSLContextRef ctx;
CFDataRef der_data;
git_cert_x509 cert_info;
@@ -57,7 +57,7 @@ static int stransport_connect(git_stream *stream)
SecTrustResultType sec_res;
OSStatus ret;
- if ((error = git_stream_connect(st->io)) < 0)
+ if (st->owned && (error = git_stream_connect(st->io)) < 0)
return error;
ret = SSLHandshake(st->ctx);
@@ -226,41 +226,38 @@ static int stransport_close(git_stream *stream)
if (ret != noErr && ret != errSSLClosedGraceful)
return stransport_error(ret);
- return git_stream_close(st->io);
+ return st->owned ? git_stream_close(st->io) : 0;
}
static void stransport_free(git_stream *stream)
{
stransport_stream *st = (stransport_stream *) stream;
- git_stream_free(st->io);
+ if (st->owned)
+ git_stream_free(st->io);
+
CFRelease(st->ctx);
if (st->der_data)
CFRelease(st->der_data);
git__free(st);
}
-int git_stransport_stream_new(git_stream **out, const char *host, const char *port)
+static int stransport_wrap(
+ git_stream **out,
+ git_stream *in,
+ const char *host,
+ int owned)
{
stransport_stream *st;
- int error;
OSStatus ret;
- assert(out && host);
+ assert(out && in && host);
st = git__calloc(1, sizeof(stransport_stream));
GITERR_CHECK_ALLOC(st);
-#ifdef GIT_CURL
- error = git_curl_stream_new(&st->io, host, port);
-#else
- error = git_socket_stream_new(&st->io, host, port);
-#endif
-
- if (error < 0){
- git__free(st);
- return error;
- }
+ st->io = in;
+ st->owned = owned;
st->ctx = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
if (!st->ctx) {
@@ -295,4 +292,32 @@ int git_stransport_stream_new(git_stream **out, const char *host, const char *po
return 0;
}
+int git_stransport_stream_wrap(
+ git_stream **out,
+ git_stream *in,
+ const char *host)
+{
+ return stransport_wrap(out, in, host, 0);
+}
+
+int git_stransport_stream_new(git_stream **out, const char *host, const char *port)
+{
+ git_stream *stream = NULL;
+ int error;
+
+ assert(out && host);
+
+ error = git_socket_stream_new(&stream, host, port);
+
+ if (!error)
+ error = stransport_wrap(out, stream, host, 1);
+
+ if (error < 0 && stream) {
+ git_stream_close(stream);
+ git_stream_free(stream);
+ }
+
+ return error;
+}
+
#endif
diff --git a/src/streams/stransport.h b/src/streams/stransport.h
index 4c02d07e8..1026e204b 100644
--- a/src/streams/stransport.h
+++ b/src/streams/stransport.h
@@ -11,6 +11,11 @@
#include "git2/sys/stream.h"
+#ifdef GIT_SECURE_TRANSPORT
+
extern int git_stransport_stream_new(git_stream **out, const char *host, const char *port);
+extern int git_stransport_stream_wrap(git_stream **out, git_stream *in, const char *host);
+
+#endif
#endif
diff --git a/src/streams/tls.c b/src/streams/tls.c
index 1bcb0d984..0ea47fb2f 100644
--- a/src/streams/tls.c
+++ b/src/streams/tls.c
@@ -5,41 +5,69 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
-#include "streams/tls.h"
-
#include "git2/errors.h"
+#include "common.h"
+#include "global.h"
+#include "streams/registry.h"
+#include "streams/tls.h"
#include "streams/mbedtls.h"
#include "streams/openssl.h"
#include "streams/stransport.h"
-static git_stream_cb tls_ctor;
-
-int git_stream_register_tls(git_stream_cb ctor)
+int git_tls_stream_new(git_stream **out, const char *host, const char *port)
{
- tls_ctor = ctor;
+ int (*init)(git_stream **, const char *, const char *) = NULL;
+ git_stream_registration custom = {0};
+ int error;
+
+ assert(out && host && port);
- return 0;
+ if ((error = git_stream_registry_lookup(&custom, GIT_STREAM_TLS)) == 0) {
+ init = custom.init;
+ } else if (error == GIT_ENOTFOUND) {
+#ifdef GIT_SECURE_TRANSPORT
+ init = git_stransport_stream_new;
+#elif defined(GIT_OPENSSL)
+ init = git_openssl_stream_new;
+#elif defined(GIT_MBEDTLS)
+ init = git_mbedtls_stream_new;
+#endif
+ } else {
+ return error;
+ }
+
+ if (!init) {
+ giterr_set(GITERR_SSL, "there is no TLS stream available");
+ return -1;
+ }
+
+ return init(out, host, port);
}
-int git_tls_stream_new(git_stream **out, const char *host, const char *port)
+int git_tls_stream_wrap(git_stream **out, git_stream *in, const char *host)
{
+ int (*wrap)(git_stream **, git_stream *, const char *) = NULL;
+ git_stream_registration custom = {0};
- if (tls_ctor)
- return tls_ctor(out, host, port);
+ assert(out && in);
+ if (git_stream_registry_lookup(&custom, GIT_STREAM_TLS) == 0) {
+ wrap = custom.wrap;
+ } else {
#ifdef GIT_SECURE_TRANSPORT
- return git_stransport_stream_new(out, host, port);
+ wrap = git_stransport_stream_wrap;
#elif defined(GIT_OPENSSL)
- return git_openssl_stream_new(out, host, port);
+ wrap = git_openssl_stream_wrap;
#elif defined(GIT_MBEDTLS)
- return git_mbedtls_stream_new(out, host, port);
-#else
- GIT_UNUSED(out);
- GIT_UNUSED(host);
- GIT_UNUSED(port);
-
- giterr_set(GITERR_SSL, "there is no TLS stream available");
- return -1;
+ wrap = git_mbedtls_stream_wrap;
#endif
+ }
+
+ if (!wrap) {
+ giterr_set(GITERR_SSL, "there is no TLS stream available");
+ return -1;
+ }
+
+ return wrap(out, in, host);
}
diff --git a/src/streams/tls.h b/src/streams/tls.h
index 6d110e8ad..465a6ea89 100644
--- a/src/streams/tls.h
+++ b/src/streams/tls.h
@@ -13,11 +13,19 @@
/**
* Create a TLS stream with the most appropriate backend available for
- * the current platform.
- *
- * This allows us to ask for a SecureTransport or OpenSSL stream
- * according to being on general Unix vs OS X.
+ * the current platform, whether that's SecureTransport on macOS,
+ * OpenSSL or mbedTLS on other Unixes, or something else entirely.
*/
extern int git_tls_stream_new(git_stream **out, const char *host, const char *port);
+/**
+ * Create a TLS stream on top of an existing insecure stream, using
+ * the most appropriate backend available for the current platform.
+ *
+ * This allows us to create a CONNECT stream on top of a proxy;
+ * using SecureTransport on macOS, OpenSSL or mbedTLS on other
+ * Unixes, or something else entirely.
+ */
+extern int git_tls_stream_wrap(git_stream **out, git_stream *in, const char *host);
+
#endif
diff --git a/src/transports/auth.c b/src/transports/auth.c
index c8e6adb12..dcc3e09a7 100644
--- a/src/transports/auth.c
+++ b/src/transports/auth.c
@@ -11,7 +11,10 @@
#include "buffer.h"
static int basic_next_token(
- git_buf *out, git_http_auth_context *ctx, git_cred *c)
+ git_buf *out,
+ git_http_auth_context *ctx,
+ const char *header_name,
+ git_cred *c)
{
git_cred_userpass_plaintext *cred;
git_buf raw = GIT_BUF_INIT;
@@ -29,7 +32,7 @@ static int basic_next_token(
git_buf_printf(&raw, "%s:%s", cred->username, cred->password);
if (git_buf_oom(&raw) ||
- git_buf_puts(out, "Authorization: Basic ") < 0 ||
+ git_buf_printf(out, "%s: Basic ", header_name) < 0 ||
git_buf_encode_base64(out, git_buf_cstr(&raw), raw.size) < 0 ||
git_buf_puts(out, "\r\n") < 0)
goto on_error;
diff --git a/src/transports/auth.h b/src/transports/auth.h
index 3b8b29eb9..e5cf7eff0 100644
--- a/src/transports/auth.h
+++ b/src/transports/auth.h
@@ -31,7 +31,7 @@ struct git_http_auth_context {
int (*set_challenge)(git_http_auth_context *ctx, const char *challenge);
/** Gets the next authentication token from the context */
- int (*next_token)(git_buf *out, git_http_auth_context *ctx, git_cred *cred);
+ int (*next_token)(git_buf *out, git_http_auth_context *ctx, const char *header_name, git_cred *cred);
/** Frees the authentication context */
void (*free)(git_http_auth_context *ctx);
diff --git a/src/transports/auth_negotiate.c b/src/transports/auth_negotiate.c
index eeabe8a6d..173ae992e 100644
--- a/src/transports/auth_negotiate.c
+++ b/src/transports/auth_negotiate.c
@@ -73,6 +73,7 @@ static int negotiate_set_challenge(
static int negotiate_next_token(
git_buf *buf,
git_http_auth_context *c,
+ const char *header_name,
git_cred *cred)
{
http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
@@ -155,7 +156,7 @@ static int negotiate_next_token(
goto done;
}
- git_buf_puts(buf, "Authorization: Negotiate ");
+ git_buf_printf(buf, "%s: Negotiate ", header_name);
git_buf_encode_base64(buf, output_token.value, output_token.length);
git_buf_puts(buf, "\r\n");
diff --git a/src/transports/http.c b/src/transports/http.c
index 7f9d35012..3a0d2cc02 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -21,7 +21,6 @@
#include "auth_negotiate.h"
#include "streams/tls.h"
#include "streams/socket.h"
-#include "streams/curl.h"
git_http_auth_scheme auth_schemes[] = {
{ GIT_AUTHTYPE_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, git_http_auth_negotiate },
@@ -37,6 +36,12 @@ static const char *receive_pack_service_url = "/git-receive-pack";
static const char *get_verb = "GET";
static const char *post_verb = "POST";
+#define AUTH_HEADER_SERVER "Authorization"
+#define AUTH_HEADER_PROXY "Proxy-Authorization"
+
+#define SERVER_TYPE_REMOTE "remote"
+#define SERVER_TYPE_PROXY "proxy"
+
#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport)
#define PARSE_ERROR_GENERIC -1
@@ -62,17 +67,32 @@ typedef struct {
unsigned chunk_buffer_len;
unsigned sent_request : 1,
received_response : 1,
- chunked : 1,
- redirect_count : 3;
+ chunked : 1;
} http_stream;
typedef struct {
+ gitno_connection_data url;
+ git_stream *stream;
+
+ git_cred *cred;
+ git_cred *url_cred;
+
+ git_vector auth_challenges;
+ git_vector auth_contexts;
+} http_server;
+
+typedef struct {
git_smart_subtransport parent;
transport_smart *owner;
- git_stream *io;
- gitno_connection_data connection_data;
+ git_stream *gitserver_stream;
bool connected;
+ http_server server;
+
+ http_server proxy;
+ char *proxy_url;
+ git_proxy_options proxy_opts;
+
/* Parser structures */
http_parser parser;
http_parser_settings settings;
@@ -81,17 +101,13 @@ typedef struct {
git_buf parse_header_value;
char parse_buffer_data[NETIO_BUFSIZE];
char *content_type;
+ char *content_length;
char *location;
- git_vector www_authenticate;
enum last_cb last_cb;
int parse_error;
int error;
- unsigned parse_finished : 1;
-
- /* Authentication */
- git_cred *cred;
- git_cred *url_cred;
- git_vector auth_contexts;
+ unsigned parse_finished : 1,
+ replay_count : 3;
} http_subtransport;
typedef struct {
@@ -124,7 +140,7 @@ static bool challenge_match(git_http_auth_scheme *scheme, void *data)
static int auth_context_match(
git_http_auth_context **out,
- http_subtransport *t,
+ http_server *server,
bool (*scheme_match)(git_http_auth_scheme *scheme, void *data),
void *data)
{
@@ -145,7 +161,7 @@ static int auth_context_match(
return 0;
/* See if authentication has already started for this scheme */
- git_vector_foreach(&t->auth_contexts, i, c) {
+ git_vector_foreach(&server->auth_contexts, i, c) {
if (c->type == scheme->type) {
context = c;
break;
@@ -153,11 +169,11 @@ static int auth_context_match(
}
if (!context) {
- if (scheme->init_context(&context, &t->connection_data) < 0)
+ if (scheme->init_context(&context, &server->url) < 0)
return -1;
else if (!context)
return 0;
- else if (git_vector_insert(&t->auth_contexts, context) < 0)
+ else if (git_vector_insert(&server->auth_contexts, context) < 0)
return -1;
}
@@ -166,32 +182,36 @@ static int auth_context_match(
return 0;
}
-static int apply_credentials(git_buf *buf, http_subtransport *t)
+static int apply_credentials(
+ git_buf *buf,
+ http_server *server,
+ const char *header_name)
{
- git_cred *cred = t->cred;
+ git_cred *cred = server->cred;
git_http_auth_context *context;
/* Apply the credentials given to us in the URL */
- if (!cred && t->connection_data.user && t->connection_data.pass) {
- if (!t->url_cred &&
- git_cred_userpass_plaintext_new(&t->url_cred,
- t->connection_data.user, t->connection_data.pass) < 0)
+ if (!cred && server->url.user && server->url.pass) {
+ if (!server->url_cred &&
+ git_cred_userpass_plaintext_new(&server->url_cred,
+ server->url.user, server->url.pass) < 0)
return -1;
- cred = t->url_cred;
+ cred = server->url_cred;
}
if (!cred)
return 0;
/* Get or create a context for the best scheme for this cred type */
- if (auth_context_match(&context, t, credtype_match, &cred->credtype) < 0)
+ if (auth_context_match(&context, server,
+ credtype_match, &cred->credtype) < 0)
return -1;
if (!context)
return 0;
- return context->next_token(buf, context, cred);
+ return context->next_token(buf, context, header_name, cred);
}
static int gen_request(
@@ -200,17 +220,26 @@ static int gen_request(
size_t content_length)
{
http_subtransport *t = OWNING_SUBTRANSPORT(s);
- const char *path = t->connection_data.path ? t->connection_data.path : "/";
+ const char *path = t->server.url.path ? t->server.url.path : "/";
size_t i;
- git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, path, s->service_url);
+ if (t->proxy_opts.type == GIT_PROXY_SPECIFIED)
+ git_buf_printf(buf, "%s %s://%s:%s%s%s HTTP/1.1\r\n",
+ s->verb,
+ t->server.url.use_ssl ? "https" : "http",
+ t->server.url.host,
+ t->server.url.port,
+ path, s->service_url);
+ else
+ git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n",
+ s->verb, path, s->service_url);
git_buf_puts(buf, "User-Agent: ");
git_http__user_agent(buf);
git_buf_puts(buf, "\r\n");
- git_buf_printf(buf, "Host: %s", t->connection_data.host);
- if (strcmp(t->connection_data.port, gitno__default_port(&t->connection_data)) != 0) {
- git_buf_printf(buf, ":%s", t->connection_data.port);
+ git_buf_printf(buf, "Host: %s", t->server.url.host);
+ if (strcmp(t->server.url.port, gitno__default_port(&t->server.url)) != 0) {
+ git_buf_printf(buf, ":%s", t->server.url.port);
}
git_buf_puts(buf, "\r\n");
@@ -230,8 +259,12 @@ static int gen_request(
git_buf_printf(buf, "%s\r\n", t->owner->custom_headers.strings[i]);
}
- /* Apply credentials to the request */
- if (apply_credentials(buf, t) < 0)
+ /* Apply proxy and server credentials to the request */
+ if (t->proxy_opts.type != GIT_PROXY_NONE &&
+ apply_credentials(buf, &t->proxy, AUTH_HEADER_PROXY) < 0)
+ return -1;
+
+ if (apply_credentials(buf, &t->server, AUTH_HEADER_SERVER) < 0)
return -1;
git_buf_puts(buf, "\r\n");
@@ -243,16 +276,16 @@ static int gen_request(
}
static int parse_authenticate_response(
- git_vector *www_authenticate,
- http_subtransport *t,
+ http_server *server,
int *allowed_types)
{
git_http_auth_context *context;
char *challenge;
size_t i;
- git_vector_foreach(www_authenticate, i, challenge) {
- if (auth_context_match(&context, t, challenge_match, challenge) < 0)
+ git_vector_foreach(&server->auth_challenges, i, challenge) {
+ if (auth_context_match(&context, server,
+ challenge_match, challenge) < 0)
return -1;
else if (!context)
continue;
@@ -273,22 +306,45 @@ static int on_header_ready(http_subtransport *t)
git_buf *value = &t->parse_header_value;
if (!strcasecmp("Content-Type", git_buf_cstr(name))) {
- if (!t->content_type) {
- t->content_type = git__strdup(git_buf_cstr(value));
- GITERR_CHECK_ALLOC(t->content_type);
+ if (t->content_type) {
+ giterr_set(GITERR_NET, "multiple Content-Type headers");
+ return -1;
}
+
+ t->content_type = git__strdup(git_buf_cstr(value));
+ GITERR_CHECK_ALLOC(t->content_type);
+ }
+ else if (!strcasecmp("Content-Length", git_buf_cstr(name))) {
+ if (t->content_length) {
+ giterr_set(GITERR_NET, "multiple Content-Length headers");
+ return -1;
+ }
+
+ t->content_length = git__strdup(git_buf_cstr(value));
+ GITERR_CHECK_ALLOC(t->content_length);
+ }
+ else if (!strcasecmp("Proxy-Authenticate", git_buf_cstr(name))) {
+ char *dup = git__strdup(git_buf_cstr(value));
+ GITERR_CHECK_ALLOC(dup);
+
+ if (git_vector_insert(&t->proxy.auth_challenges, dup) < 0)
+ return -1;
}
else if (!strcasecmp("WWW-Authenticate", git_buf_cstr(name))) {
char *dup = git__strdup(git_buf_cstr(value));
GITERR_CHECK_ALLOC(dup);
- git_vector_insert(&t->www_authenticate, dup);
+ if (git_vector_insert(&t->server.auth_challenges, dup) < 0)
+ return -1;
}
else if (!strcasecmp("Location", git_buf_cstr(name))) {
- if (!t->location) {
- t->location = git__strdup(git_buf_cstr(value));
- GITERR_CHECK_ALLOC(t->location);
+ if (t->location) {
+ giterr_set(GITERR_NET, "multiple Location headers");
+ return -1;
}
+
+ t->location = git__strdup(git_buf_cstr(value));
+ GITERR_CHECK_ALLOC(t->location);
}
return 0;
@@ -332,13 +388,78 @@ static int on_header_value(http_parser *parser, const char *str, size_t len)
return 0;
}
+GIT_INLINE(void) free_cred(git_cred **cred)
+{
+ if (*cred) {
+ git_cred_free(*cred);
+ (*cred) = NULL;
+ }
+}
+
+static int on_auth_required(
+ git_cred **creds,
+ http_parser *parser,
+ const char *url,
+ const char *type,
+ git_cred_acquire_cb callback,
+ void *callback_payload,
+ const char *username,
+ int allowed_types)
+{
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
+ int ret;
+
+ if (!allowed_types) {
+ giterr_set(GITERR_NET, "%s requested authentication but did not negotiate mechanisms", type);
+ t->parse_error = PARSE_ERROR_GENERIC;
+ return t->parse_error;
+ }
+
+ if (callback) {
+ free_cred(creds);
+ ret = callback(creds, url, username, allowed_types, callback_payload);
+
+ if (ret == GIT_PASSTHROUGH) {
+ /* treat GIT_PASSTHROUGH as if callback isn't set */
+ } else if (ret < 0) {
+ t->error = ret;
+ t->parse_error = PARSE_ERROR_EXT;
+ return t->parse_error;
+ } else {
+ assert(*creds);
+
+ if (!((*creds)->credtype & allowed_types)) {
+ giterr_set(GITERR_NET, "%s credential provider returned an invalid cred type", type);
+ t->parse_error = PARSE_ERROR_GENERIC;
+ return t->parse_error;
+ }
+
+ /* Successfully acquired a credential. */
+ t->parse_error = PARSE_ERROR_REPLAY;
+ return 0;
+ }
+ }
+
+ giterr_set(GITERR_NET, "%s authentication required but no callback set",
+ type);
+ t->parse_error = PARSE_ERROR_GENERIC;
+ return t->parse_error;
+}
+
static int on_headers_complete(http_parser *parser)
{
parser_context *ctx = (parser_context *) parser->data;
http_subtransport *t = ctx->t;
http_stream *s = ctx->s;
git_buf buf = GIT_BUF_INIT;
- int error = 0, no_callback = 0, allowed_auth_types = 0;
+ int proxy_auth_types = 0, server_auth_types = 0;
+
+ /* Enforce a reasonable cap on the number of replays */
+ if (t->replay_count++ >= GIT_HTTP_REPLAY_MAX) {
+ giterr_set(GITERR_NET, "too many redirects or authentication replays");
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
/* Both parse_header_name and parse_header_value are populated
* and ready for consumption. */
@@ -346,57 +467,36 @@ static int on_headers_complete(http_parser *parser)
if (on_header_ready(t) < 0)
return t->parse_error = PARSE_ERROR_GENERIC;
- /* Capture authentication headers which may be a 401 (authentication
- * is not complete) or a 200 (simply informing us that auth *is*
- * complete.)
+ /*
+ * Capture authentication headers for the proxy or final endpoint,
+ * these may be 407/401 (authentication is not complete) or a 200
+ * (informing us that auth has completed).
*/
- if (parse_authenticate_response(&t->www_authenticate, t,
- &allowed_auth_types) < 0)
+ if (parse_authenticate_response(&t->proxy, &proxy_auth_types) < 0 ||
+ parse_authenticate_response(&t->server, &server_auth_types) < 0)
return t->parse_error = PARSE_ERROR_GENERIC;
- /* Check for an authentication failure. */
- if (parser->status_code == 401 && get_verb == s->verb) {
- if (!t->owner->cred_acquire_cb) {
- no_callback = 1;
- } else {
- if (allowed_auth_types) {
- if (t->cred) {
- t->cred->free(t->cred);
- t->cred = NULL;
- }
-
- error = t->owner->cred_acquire_cb(&t->cred,
- t->owner->url,
- t->connection_data.user,
- allowed_auth_types,
- t->owner->cred_acquire_payload);
-
- /* treat GIT_PASSTHROUGH as if callback isn't set */
- if (error == GIT_PASSTHROUGH) {
- no_callback = 1;
- } else if (error < 0) {
- t->error = error;
- return t->parse_error = PARSE_ERROR_EXT;
- } else {
- assert(t->cred);
-
- if (!(t->cred->credtype & allowed_auth_types)) {
- giterr_set(GITERR_NET, "credentials callback returned an invalid cred type");
- return t->parse_error = PARSE_ERROR_GENERIC;
- }
-
- /* Successfully acquired a credential. */
- t->parse_error = PARSE_ERROR_REPLAY;
- return 0;
- }
- }
- }
+ /* Check for a proxy authentication failure. */
+ if (parser->status_code == 407 && get_verb == s->verb)
+ return on_auth_required(&t->proxy.cred,
+ parser,
+ t->proxy_opts.url,
+ SERVER_TYPE_PROXY,
+ t->proxy_opts.credentials,
+ t->proxy_opts.payload,
+ t->proxy.url.user,
+ proxy_auth_types);
- if (no_callback) {
- giterr_set(GITERR_NET, "authentication required but no callback set");
- return t->parse_error = PARSE_ERROR_GENERIC;
- }
- }
+ /* Check for an authentication failure. */
+ if (parser->status_code == 401 && get_verb == s->verb)
+ return on_auth_required(&t->server.cred,
+ parser,
+ t->owner->url,
+ SERVER_TYPE_REMOTE,
+ t->owner->cred_acquire_cb,
+ t->owner->cred_acquire_payload,
+ t->server.url.user,
+ server_auth_types);
/* Check for a redirect.
* Right now we only permit a redirect to the same hostname. */
@@ -407,12 +507,7 @@ static int on_headers_complete(http_parser *parser)
parser->status_code == 308) &&
t->location) {
- if (s->redirect_count >= 7) {
- giterr_set(GITERR_NET, "too many redirects");
- return t->parse_error = PARSE_ERROR_GENERIC;
- }
-
- if (gitno_connection_data_from_url(&t->connection_data, t->location, s->service_url) < 0)
+ if (gitno_connection_data_from_url(&t->server.url, t->location, s->service_url) < 0)
return t->parse_error = PARSE_ERROR_GENERIC;
/* Set the redirect URL on the stream. This is a transfer of
@@ -424,8 +519,6 @@ static int on_headers_complete(http_parser *parser)
t->location = NULL;
t->connected = 0;
- s->redirect_count++;
-
t->parse_error = PARSE_ERROR_REPLAY;
return 0;
}
@@ -492,15 +585,19 @@ static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
if (t->parse_error == PARSE_ERROR_REPLAY)
return 0;
- if (ctx->buf_size < len) {
- giterr_set(GITERR_NET, "can't fit data in the buffer");
- return t->parse_error = PARSE_ERROR_GENERIC;
+ /* If there's no buffer set, we're explicitly ignoring the body. */
+ if (ctx->buffer) {
+ if (ctx->buf_size < len) {
+ giterr_set(GITERR_NET, "can't fit data in the buffer");
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+
+ memcpy(ctx->buffer, str, len);
+ ctx->buffer += len;
+ ctx->buf_size -= len;
}
- memcpy(ctx->buffer, str, len);
*(ctx->bytes_read) += len;
- ctx->buffer += len;
- ctx->buf_size -= len;
return 0;
}
@@ -508,7 +605,7 @@ static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
static void clear_parser_state(http_subtransport *t)
{
http_parser_init(&t->parser, HTTP_RESPONSE);
- gitno_buffer_setup_fromstream(t->io,
+ gitno_buffer_setup_fromstream(t->server.stream,
&t->parse_buffer,
t->parse_buffer_data,
sizeof(t->parse_buffer_data));
@@ -526,10 +623,14 @@ static void clear_parser_state(http_subtransport *t)
git__free(t->content_type);
t->content_type = NULL;
+ git__free(t->content_length);
+ t->content_length = NULL;
+
git__free(t->location);
t->location = NULL;
- git_vector_free_deep(&t->www_authenticate);
+ git_vector_free_deep(&t->proxy.auth_challenges);
+ git_vector_free_deep(&t->server.auth_challenges);
}
static int write_chunk(git_stream *io, const char *buffer, size_t len)
@@ -560,102 +661,350 @@ static int write_chunk(git_stream *io, const char *buffer, size_t len)
return 0;
}
-static int apply_proxy_config(http_subtransport *t)
+static int load_proxy_config(http_subtransport *t)
{
int error;
- git_proxy_t proxy_type;
- if (!git_stream_supports_proxy(t->io))
+ switch (t->owner->proxy.type) {
+ case GIT_PROXY_NONE:
return 0;
- proxy_type = t->owner->proxy.type;
-
- if (proxy_type == GIT_PROXY_NONE)
- return 0;
+ case GIT_PROXY_AUTO:
+ git__free(t->proxy_url);
+ t->proxy_url = NULL;
- if (proxy_type == GIT_PROXY_AUTO) {
- char *url;
- git_proxy_options opts = GIT_PROXY_OPTIONS_INIT;
+ git_proxy_init_options(&t->proxy_opts, GIT_PROXY_OPTIONS_VERSION);
- if ((error = git_remote__get_http_proxy(t->owner->owner, !!t->connection_data.use_ssl, &url)) < 0)
+ if ((error = git_remote__get_http_proxy(t->owner->owner,
+ !!t->server.url.use_ssl, &t->proxy_url)) < 0)
return error;
- opts.credentials = t->owner->proxy.credentials;
- opts.certificate_check = t->owner->proxy.certificate_check;
- opts.payload = t->owner->proxy.payload;
- opts.type = GIT_PROXY_SPECIFIED;
- opts.url = url;
- error = git_stream_set_proxy(t->io, &opts);
- git__free(url);
+ t->proxy_opts.type = GIT_PROXY_SPECIFIED;
+ t->proxy_opts.url = t->proxy_url;
+ t->proxy_opts.credentials = t->owner->proxy.credentials;
+ t->proxy_opts.certificate_check = t->owner->proxy.certificate_check;
+ t->proxy_opts.payload = t->owner->proxy.payload;
+ break;
+
+ case GIT_PROXY_SPECIFIED:
+ memcpy(&t->proxy_opts, &t->owner->proxy, sizeof(git_proxy_options));
+ break;
+ default:
+ assert(0);
+ return -1;
+ }
+
+ if ((error = gitno_connection_data_from_url(&t->proxy.url, t->proxy_opts.url, NULL)) < 0)
return error;
+
+ if (t->proxy.url.use_ssl) {
+ giterr_set(GITERR_NET, "SSL connections to proxy are not supported");
+ return -1;
}
- return git_stream_set_proxy(t->io, &t->owner->proxy);
+ return error;
}
-static int http_connect(http_subtransport *t)
+static int check_certificate(
+ git_stream *stream,
+ gitno_connection_data *url,
+ int is_valid,
+ git_transport_certificate_check_cb cert_cb,
+ void *cert_cb_payload)
{
+ git_cert *cert;
+ git_error_state last_error = {0};
int error;
- if (t->connected &&
- http_should_keep_alive(&t->parser) &&
- t->parse_finished)
- return 0;
+ if ((error = git_stream_certificate(&cert, stream)) < 0)
+ return error;
- if (t->io) {
- git_stream_close(t->io);
- git_stream_free(t->io);
- t->io = NULL;
- t->connected = 0;
+ giterr_state_capture(&last_error, GIT_ECERTIFICATE);
+
+ error = cert_cb(cert, is_valid, url->host, cert_cb_payload);
+
+ if (error == GIT_PASSTHROUGH && !is_valid)
+ return giterr_state_restore(&last_error);
+ else if (error == GIT_PASSTHROUGH)
+ error = 0;
+ else if (error && !giterr_last())
+ giterr_set(GITERR_NET, "user rejected certificate for %s", url->host);
+
+ giterr_state_free(&last_error);
+ return error;
+}
+
+static int stream_connect(
+ git_stream *stream,
+ gitno_connection_data *url,
+ git_transport_certificate_check_cb cert_cb,
+ void *cb_payload)
+{
+ int error;
+
+ GITERR_CHECK_VERSION(stream, GIT_STREAM_VERSION, "git_stream");
+
+ error = git_stream_connect(stream);
+
+ if (error && error != GIT_ECERTIFICATE)
+ return error;
+
+ if (git_stream_is_encrypted(stream) && cert_cb != NULL)
+ error = check_certificate(stream, url, !error, cert_cb, cb_payload);
+
+ return error;
+}
+
+static int gen_connect_req(git_buf *buf, http_subtransport *t)
+{
+ git_buf_printf(buf, "CONNECT %s:%s HTTP/1.1\r\n",
+ t->server.url.host, t->server.url.port);
+
+ git_buf_puts(buf, "User-Agent: ");
+ git_http__user_agent(buf);
+ git_buf_puts(buf, "\r\n");
+
+ git_buf_printf(buf, "Host: %s\r\n", t->proxy.url.host);
+
+ if (apply_credentials(buf, &t->proxy, AUTH_HEADER_PROXY) < 0)
+ return -1;
+
+ git_buf_puts(buf, "\r\n");
+
+ return git_buf_oom(buf) ? -1 : 0;
+}
+
+static int proxy_headers_complete(http_parser *parser)
+{
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
+ int proxy_auth_types = 0;
+
+ /* Enforce a reasonable cap on the number of replays */
+ if (t->replay_count++ >= GIT_HTTP_REPLAY_MAX) {
+ giterr_set(GITERR_NET, "too many redirects or authentication replays");
+ return t->parse_error = PARSE_ERROR_GENERIC;
}
- if (t->connection_data.use_ssl) {
- error = git_tls_stream_new(&t->io, t->connection_data.host, t->connection_data.port);
- } else {
-#ifdef GIT_CURL
- error = git_curl_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
+ /* Both parse_header_name and parse_header_value are populated
+ * and ready for consumption. */
+ if (VALUE == t->last_cb)
+ if (on_header_ready(t) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ /*
+ * Capture authentication headers for the proxy or final endpoint,
+ * these may be 407/401 (authentication is not complete) or a 200
+ * (informing us that auth has completed).
+ */
+ if (parse_authenticate_response(&t->proxy, &proxy_auth_types) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ /* Check for a proxy authentication failure. */
+ if (parser->status_code == 407)
+ return on_auth_required(&t->proxy.cred,
+ parser,
+ t->proxy_opts.url,
+ SERVER_TYPE_PROXY,
+ t->proxy_opts.credentials,
+ t->proxy_opts.payload,
+ t->proxy.url.user,
+ proxy_auth_types);
+
+ if (parser->status_code != 200) {
+ giterr_set(GITERR_NET, "unexpected status code from proxy: %d",
+ parser->status_code);
+ return t->parse_error = PARSE_ERROR_GENERIC;
}
- if (error < 0)
- return error;
+ if (!t->content_length || strcmp(t->content_length, "0") == 0)
+ t->parse_finished = 1;
+
+ return 0;
+}
+
+static int proxy_connect(
+ git_stream **out, git_stream *proxy_stream, http_subtransport *t)
+{
+ git_buf request = GIT_BUF_INIT;
+ static http_parser_settings proxy_parser_settings = {0};
+ size_t bytes_read = 0, bytes_parsed;
+ parser_context ctx;
+ int error;
- GITERR_CHECK_VERSION(t->io, GIT_STREAM_VERSION, "git_stream");
+ /* Use the parser settings only to parser headers. */
+ proxy_parser_settings.on_header_field = on_header_field;
+ proxy_parser_settings.on_header_value = on_header_value;
+ proxy_parser_settings.on_headers_complete = proxy_headers_complete;
+ proxy_parser_settings.on_message_complete = on_message_complete;
- apply_proxy_config(t);
+replay:
+ clear_parser_state(t);
- error = git_stream_connect(t->io);
+ gitno_buffer_setup_fromstream(proxy_stream,
+ &t->parse_buffer,
+ t->parse_buffer_data,
+ sizeof(t->parse_buffer_data));
- if ((!error || error == GIT_ECERTIFICATE) && t->owner->certificate_check_cb != NULL &&
- git_stream_is_encrypted(t->io)) {
- git_cert *cert;
- int is_valid = (error == GIT_OK);
+ if ((error = gen_connect_req(&request, t)) < 0)
+ goto done;
- if ((error = git_stream_certificate(&cert, t->io)) < 0)
- return error;
+ if ((error = git_stream_write(proxy_stream,
+ request.ptr, request.size, 0)) < 0)
+ goto done;
- giterr_clear();
- error = t->owner->certificate_check_cb(cert, is_valid, t->connection_data.host, t->owner->message_cb_payload);
+ git_buf_dispose(&request);
- if (error == GIT_PASSTHROUGH)
- error = is_valid ? 0 : GIT_ECERTIFICATE;
+ while (!bytes_read && !t->parse_finished) {
+ t->parse_buffer.offset = 0;
- if (error < 0) {
- if (!giterr_last())
- giterr_set(GITERR_NET, "user cancelled certificate check");
+ if ((error = gitno_recv(&t->parse_buffer)) < 0)
+ goto done;
- return error;
+ /*
+ * This call to http_parser_execute will invoke the on_*
+ * callbacks. Since we don't care about the body of the response,
+ * we can set our buffer to NULL.
+ */
+ ctx.t = t;
+ ctx.s = NULL;
+ ctx.buffer = NULL;
+ ctx.buf_size = 0;
+ ctx.bytes_read = &bytes_read;
+
+ /* Set the context, call the parser, then unset the context. */
+ t->parser.data = &ctx;
+
+ bytes_parsed = http_parser_execute(&t->parser,
+ &proxy_parser_settings, t->parse_buffer.data, t->parse_buffer.offset);
+
+ t->parser.data = NULL;
+
+ /* Ensure that we didn't get a redirect; unsupported. */
+ if (t->location) {
+ giterr_set(GITERR_NET, "proxy server sent unsupported redirect during CONNECT");
+ error = -1;
+ goto done;
+ }
+
+ /* Replay the request with authentication headers. */
+ if (PARSE_ERROR_REPLAY == t->parse_error)
+ goto replay;
+
+ if (t->parse_error < 0) {
+ error = t->parse_error == PARSE_ERROR_EXT ? PARSE_ERROR_EXT : -1;
+ goto done;
+ }
+
+ if (bytes_parsed != t->parse_buffer.offset) {
+ giterr_set(GITERR_NET,
+ "HTTP parser error: %s",
+ http_errno_description((enum http_errno)t->parser.http_errno));
+ error = -1;
+ goto done;
}
}
- if (error < 0)
+ if ((error = git_tls_stream_wrap(out, proxy_stream, t->server.url.host)) == 0)
+ error = stream_connect(*out, &t->server.url,
+ t->owner->certificate_check_cb,
+ t->owner->message_cb_payload);
+
+ /*
+ * Since we've connected via a HTTPS proxy tunnel, we don't behave
+ * as if we have an HTTP proxy.
+ */
+ t->proxy_opts.type = GIT_PROXY_NONE;
+ t->replay_count = 0;
+
+done:
+ return error;
+}
+
+static int http_connect(http_subtransport *t)
+{
+ gitno_connection_data *url;
+ git_stream *proxy_stream = NULL, *stream = NULL;
+ git_transport_certificate_check_cb cert_cb;
+ void *cb_payload;
+ int error;
+
+ if (t->connected &&
+ http_should_keep_alive(&t->parser) &&
+ t->parse_finished)
+ return 0;
+
+ if ((error = load_proxy_config(t)) < 0)
return error;
+ if (t->server.stream) {
+ git_stream_close(t->server.stream);
+ git_stream_free(t->server.stream);
+ t->server.stream = NULL;
+ }
+
+ if (t->proxy.stream) {
+ git_stream_close(t->proxy.stream);
+ git_stream_free(t->proxy.stream);
+ t->proxy.stream = NULL;
+ }
+
+ t->connected = 0;
+
+ if (t->proxy_opts.type == GIT_PROXY_SPECIFIED) {
+ url = &t->proxy.url;
+ cert_cb = t->proxy_opts.certificate_check;
+ cb_payload = t->proxy_opts.payload;
+ } else {
+ url = &t->server.url;
+ cert_cb = t->owner->certificate_check_cb;
+ cb_payload = t->owner->message_cb_payload;
+ }
+
+ if (url->use_ssl)
+ error = git_tls_stream_new(&stream, url->host, url->port);
+ else
+ error = git_socket_stream_new(&stream, url->host, url->port);
+
+ if (error < 0)
+ goto on_error;
+
+ if ((error = stream_connect(stream, url, cert_cb, cb_payload)) < 0)
+ goto on_error;
+
+ /*
+ * At this point we have a connection to the remote server or to
+ * a proxy. If it's a proxy and the remote server is actually
+ * an HTTPS connection, then we need to build a CONNECT tunnel.
+ */
+ if (t->proxy_opts.type == GIT_PROXY_SPECIFIED &&
+ t->server.url.use_ssl) {
+ proxy_stream = stream;
+ stream = NULL;
+
+ if ((error = proxy_connect(&stream, proxy_stream, t)) < 0)
+ goto on_error;
+ }
+
+ t->proxy.stream = proxy_stream;
+ t->server.stream = stream;
t->connected = 1;
+ t->replay_count = 0;
return 0;
+
+on_error:
+ if (stream) {
+ git_stream_close(stream);
+ git_stream_free(stream);
+ }
+
+ if (proxy_stream) {
+ git_stream_close(proxy_stream);
+ git_stream_free(proxy_stream);
+ }
+
+ return error;
}
static int http_stream_read(
@@ -682,7 +1031,8 @@ replay:
if (gen_request(&request, s, 0) < 0)
return -1;
- if (git_stream_write(t->io, request.ptr, request.size, 0) < 0) {
+ if (git_stream_write(t->server.stream,
+ request.ptr, request.size, 0) < 0) {
git_buf_dispose(&request);
return -1;
}
@@ -698,13 +1048,14 @@ replay:
/* Flush, if necessary */
if (s->chunk_buffer_len > 0 &&
- write_chunk(t->io, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ write_chunk(t->server.stream,
+ s->chunk_buffer, s->chunk_buffer_len) < 0)
return -1;
s->chunk_buffer_len = 0;
/* Write the final chunk. */
- if (git_stream_write(t->io, "0\r\n\r\n", 5, 0) < 0)
+ if (git_stream_write(t->server.stream, "0\r\n\r\n", 5, 0) < 0)
return -1;
}
@@ -803,7 +1154,8 @@ static int http_stream_write_chunked(
if (gen_request(&request, s, 0) < 0)
return -1;
- if (git_stream_write(t->io, request.ptr, request.size, 0) < 0) {
+ if (git_stream_write(t->server.stream,
+ request.ptr, request.size, 0) < 0) {
git_buf_dispose(&request);
return -1;
}
@@ -816,14 +1168,15 @@ static int http_stream_write_chunked(
if (len > CHUNK_SIZE) {
/* Flush, if necessary */
if (s->chunk_buffer_len > 0) {
- if (write_chunk(t->io, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ if (write_chunk(t->server.stream,
+ s->chunk_buffer, s->chunk_buffer_len) < 0)
return -1;
s->chunk_buffer_len = 0;
}
/* Write chunk directly */
- if (write_chunk(t->io, buffer, len) < 0)
+ if (write_chunk(t->server.stream, buffer, len) < 0)
return -1;
}
else {
@@ -840,7 +1193,8 @@ static int http_stream_write_chunked(
/* Is the buffer full? If so, then flush */
if (CHUNK_SIZE == s->chunk_buffer_len) {
- if (write_chunk(t->io, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ if (write_chunk(t->server.stream,
+ s->chunk_buffer, s->chunk_buffer_len) < 0)
return -1;
s->chunk_buffer_len = 0;
@@ -876,10 +1230,11 @@ static int http_stream_write_single(
if (gen_request(&request, s, len) < 0)
return -1;
- if (git_stream_write(t->io, request.ptr, request.size, 0) < 0)
+ if (git_stream_write(t->server.stream,
+ request.ptr, request.size, 0) < 0)
goto on_error;
- if (len && git_stream_write(t->io, buffer, len, 0) < 0)
+ if (len && git_stream_write(t->server.stream, buffer, len, 0) < 0)
goto on_error;
git_buf_dispose(&request);
@@ -1010,13 +1365,21 @@ static int http_action(
http_subtransport *t = (http_subtransport *)subtransport;
int ret;
- if (!stream)
- return -1;
+ assert(stream);
- if ((!t->connection_data.host || !t->connection_data.port || !t->connection_data.path) &&
- (ret = gitno_connection_data_from_url(&t->connection_data, url, NULL)) < 0)
+ /*
+ * If we've seen a redirect then preserve the location that we've
+ * been given. This is important to continue authorization against
+ * the redirect target, not the user-given source; the endpoint may
+ * have redirected us from HTTP->HTTPS and is using an auth mechanism
+ * that would be insecure in plaintext (eg, HTTP Basic).
+ */
+ if ((!t->server.url.host || !t->server.url.port || !t->server.url.path) &&
+ (ret = gitno_connection_data_from_url(&t->server.url, url, NULL)) < 0)
return ret;
+ assert(t->server.url.host && t->server.url.port && t->server.url.path);
+
if ((ret = http_connect(t)) < 0)
return ret;
@@ -1038,41 +1401,55 @@ static int http_action(
return -1;
}
-static int http_close(git_smart_subtransport *subtransport)
+static void free_auth_contexts(git_vector *contexts)
{
- http_subtransport *t = (http_subtransport *) subtransport;
git_http_auth_context *context;
size_t i;
+ git_vector_foreach(contexts, i, context) {
+ if (context->free)
+ context->free(context);
+ }
+
+ git_vector_clear(contexts);
+}
+
+static int http_close(git_smart_subtransport *subtransport)
+{
+ http_subtransport *t = (http_subtransport *) subtransport;
+
clear_parser_state(t);
t->connected = 0;
- if (t->io) {
- git_stream_close(t->io);
- git_stream_free(t->io);
- t->io = NULL;
+ if (t->server.stream) {
+ git_stream_close(t->server.stream);
+ git_stream_free(t->server.stream);
+ t->server.stream = NULL;
}
- if (t->cred) {
- t->cred->free(t->cred);
- t->cred = NULL;
+ if (t->proxy.stream) {
+ git_stream_close(t->proxy.stream);
+ git_stream_free(t->proxy.stream);
+ t->proxy.stream = NULL;
}
- if (t->url_cred) {
- t->url_cred->free(t->url_cred);
- t->url_cred = NULL;
- }
+ free_cred(&t->server.cred);
+ free_cred(&t->server.url_cred);
+ free_cred(&t->proxy.cred);
+ free_cred(&t->proxy.url_cred);
- git_vector_foreach(&t->auth_contexts, i, context) {
- if (context->free)
- context->free(context);
- }
+ free_auth_contexts(&t->server.auth_contexts);
+ free_auth_contexts(&t->proxy.auth_contexts);
+
+ gitno_connection_data_free_ptrs(&t->server.url);
+ memset(&t->server.url, 0x0, sizeof(gitno_connection_data));
- git_vector_clear(&t->auth_contexts);
+ gitno_connection_data_free_ptrs(&t->proxy.url);
+ memset(&t->proxy.url, 0x0, sizeof(gitno_connection_data));
- gitno_connection_data_free_ptrs(&t->connection_data);
- memset(&t->connection_data, 0x0, sizeof(gitno_connection_data));
+ git__free(t->proxy_url);
+ t->proxy_url = NULL;
return 0;
}
@@ -1083,7 +1460,8 @@ static void http_free(git_smart_subtransport *subtransport)
http_close(subtransport);
- git_vector_free(&t->auth_contexts);
+ git_vector_free(&t->server.auth_contexts);
+ git_vector_free(&t->proxy.auth_contexts);
git__free(t);
}
diff --git a/src/transports/http.h b/src/transports/http.h
index 6c4ecc9a4..b09475755 100644
--- a/src/transports/http.h
+++ b/src/transports/http.h
@@ -10,6 +10,8 @@
#include "buffer.h"
+#define GIT_HTTP_REPLAY_MAX 7
+
GIT_INLINE(int) git_http__user_agent(git_buf *buf)
{
const char *ua = git_libgit2__user_agent();
diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c
index 5e7bde73c..30e2ecb73 100644
--- a/src/transports/winhttp.c
+++ b/src/transports/winhttp.c
@@ -932,7 +932,7 @@ static int winhttp_stream_read(
replay:
/* Enforce a reasonable cap on the number of replays */
- if (++replay_count >= 7) {
+ if (replay_count++ >= GIT_HTTP_REPLAY_MAX) {
giterr_set(GITERR_NET, "too many redirects or authentication replays");
return -1;
}