diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a0ffdd42..7a7a943e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,12 @@ ENDIF() # Find required dependencies INCLUDE_DIRECTORIES(src include deps/http-parser) -FILE(GLOB SRC_HTTP deps/http-parser/*.c) +IF (WIN32 AND NOT MINGW) + ADD_DEFINITIONS(-DGIT_WINHTTP) +ELSE () + FIND_PACKAGE(OpenSSL) + FILE(GLOB SRC_HTTP deps/http-parser/*.c) +ENDIF() # Specify sha1 implementation IF (SHA1_TYPE STREQUAL "ppc") @@ -75,7 +80,7 @@ OPTION (PROFILE "Generate profiling information" OFF) # Platform specific compilation flags IF (MSVC) - # Not using __stdcall with the CRT causes problems + # Default to stdcall, as that's what the CLR expects and how the Windows API is built OPTION (STDCALL "Buildl libgit2 with the __stdcall convention" ON) SET(CMAKE_C_FLAGS "/W4 /MP /nologo /Zi ${CMAKE_C_FLAGS}") @@ -106,7 +111,6 @@ IF (NOT CMAKE_BUILD_TYPE) SET(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) ENDIF () -FIND_PACKAGE(OpenSSL) IF (OPENSSL_FOUND) ADD_DEFINITIONS(-DGIT_SSL) INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) diff --git a/src/transports/http.c b/src/transports/http.c index de33f56ea..f1619c51f 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -4,7 +4,6 @@ * 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 #include "git2.h" #include "http_parser.h" @@ -20,6 +19,13 @@ #include "filebuf.h" #include "repository.h" #include "protocol.h" +#if GIT_WINHTTP +# include +# pragma comment(lib, "winhttp.lib") +#endif + +#define WIDEN2(s) L ## s +#define WIDEN(s) WIDEN2(s) enum last_cb { NONE, @@ -47,6 +53,11 @@ typedef struct { #ifdef GIT_WIN32 WSADATA wsd; #endif +#ifdef GIT_WINHTTP + HINTERNET session; + HINTERNET connection; + HINTERNET request; +#endif } transport_http; static int gen_request(git_buf *buf, const char *path, const char *host, const char *op, @@ -77,17 +88,158 @@ static int gen_request(git_buf *buf, const char *path, const char *host, const c return 0; } -static int do_connect(transport_http *t, const char *host, const char *port) +static int send_request(transport_http *t, const char *service, void *data, ssize_t content_length, int ls) { +#ifndef GIT_WINHTTP + git_buf request = GIT_BUF_INIT; + const char *verb; + + verb = ls ? "GET" : "POST"; + /* Generate and send the HTTP request */ + if (gen_request(&request, t->path, t->host, verb, service, content_length, ls) < 0) { + giterr_set(GITERR_NET, "Failed to generate request"); + return -1; + } + + + if (gitno_send((git_transport *) t, request.ptr, request.size, 0) < 0) { + git_buf_free(&request); + return -1; + } + + if (content_length) { + if (gitno_send((git_transport *) t, data, content_length, 0) < 0) + return -1; + } + + return 0; +#else + wchar_t *url, *verb, *ct; + git_buf buf = GIT_BUF_INIT; + BOOL ret; + DWORD flags; + void *buffer; + wchar_t *types[] = { + L"*/*", + NULL, + }; + + verb = ls ? L"GET" : L"POST"; + buffer = data ? data : WINHTTP_NO_REQUEST_DATA; + flags = t->parent.use_ssl ? WINHTTP_FLAG_SECURE : 0; + + if (ls) + git_buf_printf(&buf, "%s/info/refs?service=git-%s", t->path, service); + else + git_buf_printf(&buf, "%s/git-%s", t->path, service); + + if (git_buf_oom(&buf)) + return -1; + + url = gitwin_to_utf16(git_buf_cstr(&buf)); + if (!url) + goto on_error; + + t->request = WinHttpOpenRequest(t->connection, verb, url, NULL, WINHTTP_NO_REFERER, types, flags); + git__free(url); + if (t->request == NULL) { + git_buf_free(&buf); + giterr_set(GITERR_OS, "Failed to open request"); + return -1; + } + + git_buf_clear(&buf); + if (git_buf_printf(&buf, "Content-Type: application/x-git-%s-request", service) < 0) + goto on_error; + ct = gitwin_to_utf16(git_buf_cstr(&buf)); + if (!ct) + goto on_error; + + if (WinHttpAddRequestHeaders(t->request, ct, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD) == FALSE) { + giterr_set(GITERR_OS, "Failed to add a header to the request"); + goto on_error; + } + + if (!t->parent.check_cert) { + int flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_UNKNOWN_CA; + if (WinHttpSetOption(t->request, WINHTTP_OPTION_SECURITY_FLAGS, &flags, sizeof(flags)) == FALSE) { + giterr_set(GITERR_OS, "Failed to set options to ignore cert errors"); + goto on_error; + } + } + + if (WinHttpSendRequest(t->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, + data, content_length, content_length, 0) == FALSE) { + giterr_set(GITERR_OS, "Failed to send request"); + goto on_error; + } + + ret = WinHttpReceiveResponse(t->request, NULL); + if (ret == FALSE) { + giterr_set(GITERR_OS, "Failed to receive response"); + goto on_error; + } + + return 0; + +on_error: + git_buf_free(&buf); + if (t->request) + WinHttpCloseHandle(t->request); + t->request = NULL; + return -1; +#endif +} + +static int do_connect(transport_http *t) +{ +#ifndef GIT_WINHTTP if (t->parent.connected && http_should_keep_alive(&t->parser)) return 0; - if (gitno_connect((git_transport *) t, host, port) < 0) + if (gitno_connect((git_transport *) t, t->host, t->port) < 0) return -1; t->parent.connected = 1; return 0; +#else + wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")"; + wchar_t *host; + int32_t port; + + t->session = WinHttpOpen(ua, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); + + if (t->session == NULL) { + giterr_set(GITERR_OS, "Failed to init WinHTTP"); + goto on_error; + } + + host = gitwin_to_utf16(t->host); + if (host == NULL) + goto on_error; + + if (git__strtol32(&port, t->port, NULL, 10) < 0) + goto on_error; + + t->connection = WinHttpConnect(t->session, host, port, 0); + git__free(host); + if (t->connection == NULL) { + giterr_set(GITERR_OS, "Failed to connect to host"); + goto on_error; + } + + t->parent.connected = 1; + return 0; + +on_error: + if (t->session) { + WinHttpCloseHandle(t->session); + t->session = NULL; + } + return -1; +#endif } /* @@ -216,13 +368,18 @@ static int http_recv_cb(gitno_buffer *buf) git_transport *transport = (git_transport *) buf->cb_data; transport_http *t = (transport_http *) transport; size_t old_len; - gitno_buffer inner; char buffer[2048]; +#ifdef GIT_WINHTTP + DWORD recvd; +#else + gitno_buffer inner; int error; +#endif if (t->transfer_finished) return 0; +#ifndef GIT_WINHTTP gitno_buffer_setup(transport, &inner, buffer, sizeof(buffer)); if ((error = gitno_recv(&inner)) < 0) @@ -232,6 +389,21 @@ static int http_recv_cb(gitno_buffer *buf) http_parser_execute(&t->parser, &t->settings, inner.data, inner.offset); if (t->error < 0) return t->error; +#else + old_len = buf->offset; + if (WinHttpReadData(t->request, buffer, sizeof(buffer), &recvd) == FALSE) { + giterr_set(GITERR_OS, "Failed to read data from the network"); + return t->error = -1; + } + + if (buf->len - buf->offset < recvd) { + giterr_set(GITERR_NET, "Can't fit data in the buffer"); + return t->error = -1; + } + + memcpy(buf->data + buf->offset, buffer, recvd); + buf->offset += recvd; +#endif return (int)(buf->offset - old_len); } @@ -241,6 +413,8 @@ static void setup_gitno_buffer(git_transport *transport) { transport_http *t = (transport_http *) transport; + /* WinHTTP takes care of this for us */ +#ifndef GIT_WINHTTP http_parser_init(&t->parser, HTTP_RESPONSE); t->parser.data = t; t->transfer_finished = 0; @@ -250,6 +424,7 @@ static void setup_gitno_buffer(git_transport *transport) t->settings.on_headers_complete = on_headers_complete; t->settings.on_body = on_body_fill_buffer; t->settings.on_message_complete = on_message_complete; +#endif gitno_buffer_setup_callback(transport, &transport->buffer, t->buffer, sizeof(t->buffer), http_recv_cb, t); } @@ -289,17 +464,10 @@ static int http_connect(git_transport *transport, int direction) t->service = git__strdup(service); GITERR_CHECK_ALLOC(t->service); - if ((ret = do_connect(t, t->host, t->port)) < 0) + if ((ret = do_connect(t)) < 0) goto cleanup; - /* Generate and send the HTTP request */ - if ((ret = gen_request(&request, t->path, t->host, "GET", service, 0, 1)) < 0) { - giterr_set(GITERR_NET, "Failed to generate request"); - goto cleanup; - } - - - if (gitno_send(transport, request.ptr, request.size, 0) < 0) + if ((ret = send_request(t, "upload-pack", NULL, 0, 1)) < 0) goto cleanup; setup_gitno_buffer(transport); @@ -332,36 +500,24 @@ cleanup: static int http_negotiation_step(struct git_transport *transport, void *data, size_t len) { transport_http *t = (transport_http *) transport; - git_buf request = GIT_BUF_INIT; int ret; /* First, send the data as a HTTP POST request */ - if ((ret = do_connect(t, t->host, t->port)) < 0) + if ((ret = do_connect(t)) < 0) return -1; - if ((ret = gen_request(&request, t->path, t->host, "POST", "upload-pack", len, 0)) < 0) - goto on_error; - - if ((ret = gitno_send(transport, request.ptr, request.size, 0)) < 0) - goto on_error; - - if ((ret = gitno_send(transport, data, len, 0)) < 0) - goto on_error; - - git_buf_free(&request); + if (send_request(t, "upload-pack", data, len, 0) < 0) + return -1; /* Then we need to set up the buffer to grab data from the HTTP response */ setup_gitno_buffer(transport); return 0; - -on_error: - git_buf_free(&request); - return -1; } static int http_close(git_transport *transport) { +#ifndef GIT_WINHTTP if (gitno_ssl_teardown(transport) < 0) return -1; @@ -369,6 +525,16 @@ static int http_close(git_transport *transport) giterr_set(GITERR_OS, "Failed to close the socket: %s", strerror(errno)); return -1; } +#else + transport_http *t = (transport_http *) transport; + + if (t->request) + WinHttpCloseHandle(t->request); + if (t->connection) + WinHttpCloseHandle(t->connection); + if (t->session) + WinHttpCloseHandle(t->session); +#endif transport->connected = 0; @@ -445,7 +611,7 @@ int git_transport_http(git_transport **out) int git_transport_https(git_transport **out) { -#ifdef GIT_SSL +#if defined(GIT_SSL) || defined(GIT_WINHTTP) transport_http *t; if (git_transport_http((git_transport **)&t) < 0) return -1; diff --git a/src/util.c b/src/util.c index 51bf843de..719714105 100644 --- a/src/util.c +++ b/src/util.c @@ -28,7 +28,7 @@ int git_libgit2_capabilities() #ifdef GIT_THREADS | GIT_CAP_THREADS #endif -#ifdef GIT_SSL +#if defined(GIT_SSL) || defined(GIT_WINHTTP) | GIT_CAP_HTTPS #endif ;