summaryrefslogtreecommitdiff
path: root/src/libgit2/transports/http.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libgit2/transports/http.c')
-rw-r--r--src/libgit2/transports/http.c760
1 files changed, 760 insertions, 0 deletions
diff --git a/src/libgit2/transports/http.c b/src/libgit2/transports/http.c
new file mode 100644
index 000000000..7db5582ca
--- /dev/null
+++ b/src/libgit2/transports/http.c
@@ -0,0 +1,760 @@
+/*
+ * 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 "common.h"
+
+#ifndef GIT_WINHTTP
+
+#include "http_parser.h"
+#include "net.h"
+#include "netops.h"
+#include "remote.h"
+#include "smart.h"
+#include "auth.h"
+#include "http.h"
+#include "auth_negotiate.h"
+#include "auth_ntlm.h"
+#include "trace.h"
+#include "streams/tls.h"
+#include "streams/socket.h"
+#include "httpclient.h"
+#include "git2/sys/credential.h"
+
+bool git_http__expect_continue = false;
+
+typedef enum {
+ HTTP_STATE_NONE = 0,
+ HTTP_STATE_SENDING_REQUEST,
+ HTTP_STATE_RECEIVING_RESPONSE,
+ HTTP_STATE_DONE
+} http_state;
+
+typedef struct {
+ git_http_method method;
+ const char *url;
+ const char *request_type;
+ const char *response_type;
+ unsigned int initial : 1,
+ chunked : 1;
+} http_service;
+
+typedef struct {
+ git_smart_subtransport_stream parent;
+ const http_service *service;
+ http_state state;
+ unsigned replay_count;
+} http_stream;
+
+typedef struct {
+ git_net_url url;
+
+ git_credential *cred;
+ unsigned auth_schemetypes;
+ unsigned url_cred_presented : 1;
+} http_server;
+
+typedef struct {
+ git_smart_subtransport parent;
+ transport_smart *owner;
+
+ http_server server;
+ http_server proxy;
+
+ git_http_client *http_client;
+} http_subtransport;
+
+static const http_service upload_pack_ls_service = {
+ GIT_HTTP_METHOD_GET, "/info/refs?service=git-upload-pack",
+ NULL,
+ "application/x-git-upload-pack-advertisement",
+ 1,
+ 0
+};
+static const http_service upload_pack_service = {
+ GIT_HTTP_METHOD_POST, "/git-upload-pack",
+ "application/x-git-upload-pack-request",
+ "application/x-git-upload-pack-result",
+ 0,
+ 0
+};
+static const http_service receive_pack_ls_service = {
+ GIT_HTTP_METHOD_GET, "/info/refs?service=git-receive-pack",
+ NULL,
+ "application/x-git-receive-pack-advertisement",
+ 1,
+ 0
+};
+static const http_service receive_pack_service = {
+ GIT_HTTP_METHOD_POST, "/git-receive-pack",
+ "application/x-git-receive-pack-request",
+ "application/x-git-receive-pack-result",
+ 0,
+ 1
+};
+
+#define SERVER_TYPE_REMOTE "remote"
+#define SERVER_TYPE_PROXY "proxy"
+
+#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport)
+
+static int apply_url_credentials(
+ git_credential **cred,
+ unsigned int allowed_types,
+ const char *username,
+ const char *password)
+{
+ GIT_ASSERT_ARG(username);
+
+ if (!password)
+ password = "";
+
+ if (allowed_types & GIT_CREDENTIAL_USERPASS_PLAINTEXT)
+ return git_credential_userpass_plaintext_new(cred, username, password);
+
+ if ((allowed_types & GIT_CREDENTIAL_DEFAULT) && *username == '\0' && *password == '\0')
+ return git_credential_default_new(cred);
+
+ return GIT_PASSTHROUGH;
+}
+
+GIT_INLINE(void) free_cred(git_credential **cred)
+{
+ if (*cred) {
+ git_credential_free(*cred);
+ (*cred) = NULL;
+ }
+}
+
+static int handle_auth(
+ http_server *server,
+ const char *server_type,
+ const char *url,
+ unsigned int allowed_schemetypes,
+ unsigned int allowed_credtypes,
+ git_credential_acquire_cb callback,
+ void *callback_payload)
+{
+ int error = 1;
+
+ if (server->cred)
+ free_cred(&server->cred);
+
+ /* Start with URL-specified credentials, if there were any. */
+ if ((allowed_credtypes & GIT_CREDENTIAL_USERPASS_PLAINTEXT) &&
+ !server->url_cred_presented &&
+ server->url.username) {
+ error = apply_url_credentials(&server->cred, allowed_credtypes, server->url.username, server->url.password);
+ server->url_cred_presented = 1;
+
+ /* treat GIT_PASSTHROUGH as if callback isn't set */
+ if (error == GIT_PASSTHROUGH)
+ error = 1;
+ }
+
+ if (error > 0 && callback) {
+ error = callback(&server->cred, url, server->url.username, allowed_credtypes, callback_payload);
+
+ /* treat GIT_PASSTHROUGH as if callback isn't set */
+ if (error == GIT_PASSTHROUGH)
+ error = 1;
+ }
+
+ if (error > 0) {
+ git_error_set(GIT_ERROR_HTTP, "%s authentication required but no callback set", server_type);
+ error = GIT_EAUTH;
+ }
+
+ if (!error)
+ server->auth_schemetypes = allowed_schemetypes;
+
+ return error;
+}
+
+GIT_INLINE(int) handle_remote_auth(
+ http_stream *stream,
+ git_http_response *response)
+{
+ http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
+ git_remote_connect_options *connect_opts = &transport->owner->connect_opts;
+
+ if (response->server_auth_credtypes == 0) {
+ git_error_set(GIT_ERROR_HTTP, "server requires authentication that we do not support");
+ return GIT_EAUTH;
+ }
+
+ /* Otherwise, prompt for credentials. */
+ return handle_auth(
+ &transport->server,
+ SERVER_TYPE_REMOTE,
+ transport->owner->url,
+ response->server_auth_schemetypes,
+ response->server_auth_credtypes,
+ connect_opts->callbacks.credentials,
+ connect_opts->callbacks.payload);
+}
+
+GIT_INLINE(int) handle_proxy_auth(
+ http_stream *stream,
+ git_http_response *response)
+{
+ http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
+ git_remote_connect_options *connect_opts = &transport->owner->connect_opts;
+
+ if (response->proxy_auth_credtypes == 0) {
+ git_error_set(GIT_ERROR_HTTP, "proxy requires authentication that we do not support");
+ return GIT_EAUTH;
+ }
+
+ /* Otherwise, prompt for credentials. */
+ return handle_auth(
+ &transport->proxy,
+ SERVER_TYPE_PROXY,
+ connect_opts->proxy_opts.url,
+ response->server_auth_schemetypes,
+ response->proxy_auth_credtypes,
+ connect_opts->proxy_opts.credentials,
+ connect_opts->proxy_opts.payload);
+}
+
+static bool allow_redirect(http_stream *stream)
+{
+ http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
+
+ switch (transport->owner->connect_opts.follow_redirects) {
+ case GIT_REMOTE_REDIRECT_INITIAL:
+ return (stream->service->initial == 1);
+ case GIT_REMOTE_REDIRECT_ALL:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int handle_response(
+ bool *complete,
+ http_stream *stream,
+ git_http_response *response,
+ bool allow_replay)
+{
+ http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
+ int error;
+
+ *complete = false;
+
+ if (allow_replay && git_http_response_is_redirect(response)) {
+ if (!response->location) {
+ git_error_set(GIT_ERROR_HTTP, "redirect without location");
+ return -1;
+ }
+
+ if (git_net_url_apply_redirect(&transport->server.url, response->location, allow_redirect(stream), stream->service->url) < 0) {
+ return -1;
+ }
+
+ return 0;
+ } else if (git_http_response_is_redirect(response)) {
+ git_error_set(GIT_ERROR_HTTP, "unexpected redirect");
+ return -1;
+ }
+
+ /* If we're in the middle of challenge/response auth, continue. */
+ if (allow_replay && response->resend_credentials) {
+ return 0;
+ } else if (allow_replay && response->status == GIT_HTTP_STATUS_UNAUTHORIZED) {
+ if ((error = handle_remote_auth(stream, response)) < 0)
+ return error;
+
+ return git_http_client_skip_body(transport->http_client);
+ } else if (allow_replay && response->status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
+ if ((error = handle_proxy_auth(stream, response)) < 0)
+ return error;
+
+ return git_http_client_skip_body(transport->http_client);
+ } else if (response->status == GIT_HTTP_STATUS_UNAUTHORIZED ||
+ response->status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
+ git_error_set(GIT_ERROR_HTTP, "unexpected authentication failure");
+ return GIT_EAUTH;
+ }
+
+ if (response->status != GIT_HTTP_STATUS_OK) {
+ git_error_set(GIT_ERROR_HTTP, "unexpected http status code: %d", response->status);
+ return -1;
+ }
+
+ /* The response must contain a Content-Type header. */
+ if (!response->content_type) {
+ git_error_set(GIT_ERROR_HTTP, "no content-type header in response");
+ return -1;
+ }
+
+ /* The Content-Type header must match our expectation. */
+ if (strcmp(response->content_type, stream->service->response_type) != 0) {
+ git_error_set(GIT_ERROR_HTTP, "invalid content-type: '%s'", response->content_type);
+ return -1;
+ }
+
+ *complete = true;
+ stream->state = HTTP_STATE_RECEIVING_RESPONSE;
+ return 0;
+}
+
+static int lookup_proxy(
+ bool *out_use,
+ http_subtransport *transport)
+{
+ git_remote_connect_options *connect_opts = &transport->owner->connect_opts;
+ const char *proxy;
+ git_remote *remote;
+ char *config = NULL;
+ int error = 0;
+
+ *out_use = false;
+ git_net_url_dispose(&transport->proxy.url);
+
+ switch (connect_opts->proxy_opts.type) {
+ case GIT_PROXY_SPECIFIED:
+ proxy = connect_opts->proxy_opts.url;
+ break;
+
+ case GIT_PROXY_AUTO:
+ remote = transport->owner->owner;
+
+ error = git_remote__http_proxy(&config, remote, &transport->server.url);
+
+ if (error || !config)
+ goto done;
+
+ proxy = config;
+ break;
+
+ default:
+ return 0;
+ }
+
+ if (!proxy ||
+ (error = git_net_url_parse(&transport->proxy.url, proxy)) < 0)
+ goto done;
+
+ *out_use = true;
+
+done:
+ git__free(config);
+ return error;
+}
+
+static int generate_request(
+ git_net_url *url,
+ git_http_request *request,
+ http_stream *stream,
+ size_t len)
+{
+ http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
+ bool use_proxy = false;
+ int error;
+
+ if ((error = git_net_url_joinpath(url,
+ &transport->server.url, stream->service->url)) < 0 ||
+ (error = lookup_proxy(&use_proxy, transport)) < 0)
+ return error;
+
+ request->method = stream->service->method;
+ request->url = url;
+ request->credentials = transport->server.cred;
+ request->proxy = use_proxy ? &transport->proxy.url : NULL;
+ request->proxy_credentials = transport->proxy.cred;
+ request->custom_headers = &transport->owner->connect_opts.custom_headers;
+
+ if (stream->service->method == GIT_HTTP_METHOD_POST) {
+ request->chunked = stream->service->chunked;
+ request->content_length = stream->service->chunked ? 0 : len;
+ request->content_type = stream->service->request_type;
+ request->accept = stream->service->response_type;
+ request->expect_continue = git_http__expect_continue;
+ }
+
+ return 0;
+}
+
+/*
+ * Read from an HTTP transport - for the first invocation of this function
+ * (ie, when stream->state == HTTP_STATE_NONE), we'll send a GET request
+ * to the remote host. We will stream that data back on all subsequent
+ * calls.
+ */
+static int http_stream_read(
+ git_smart_subtransport_stream *s,
+ char *buffer,
+ size_t buffer_size,
+ size_t *out_len)
+{
+ http_stream *stream = (http_stream *)s;
+ http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
+ git_net_url url = GIT_NET_URL_INIT;
+ git_net_url proxy_url = GIT_NET_URL_INIT;
+ git_http_request request = {0};
+ git_http_response response = {0};
+ bool complete;
+ int error;
+
+ *out_len = 0;
+
+ if (stream->state == HTTP_STATE_NONE) {
+ stream->state = HTTP_STATE_SENDING_REQUEST;
+ stream->replay_count = 0;
+ }
+
+ /*
+ * Formulate the URL, send the request and read the response
+ * headers. Some of the request body may also be read.
+ */
+ while (stream->state == HTTP_STATE_SENDING_REQUEST &&
+ stream->replay_count < GIT_HTTP_REPLAY_MAX) {
+ git_net_url_dispose(&url);
+ git_net_url_dispose(&proxy_url);
+ git_http_response_dispose(&response);
+
+ if ((error = generate_request(&url, &request, stream, 0)) < 0 ||
+ (error = git_http_client_send_request(
+ transport->http_client, &request)) < 0 ||
+ (error = git_http_client_read_response(
+ &response, transport->http_client)) < 0 ||
+ (error = handle_response(&complete, stream, &response, true)) < 0)
+ goto done;
+
+ if (complete)
+ break;
+
+ stream->replay_count++;
+ }
+
+ if (stream->state == HTTP_STATE_SENDING_REQUEST) {
+ git_error_set(GIT_ERROR_HTTP, "too many redirects or authentication replays");
+ error = GIT_ERROR; /* not GIT_EAUTH, because the exact cause is unclear */
+ goto done;
+ }
+
+ GIT_ASSERT(stream->state == HTTP_STATE_RECEIVING_RESPONSE);
+
+ error = git_http_client_read_body(transport->http_client, buffer, buffer_size);
+
+ if (error > 0) {
+ *out_len = error;
+ error = 0;
+ }
+
+done:
+ git_net_url_dispose(&url);
+ git_net_url_dispose(&proxy_url);
+ git_http_response_dispose(&response);
+
+ return error;
+}
+
+static bool needs_probe(http_stream *stream)
+{
+ http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
+
+ return (transport->server.auth_schemetypes == GIT_HTTP_AUTH_NTLM ||
+ transport->server.auth_schemetypes == GIT_HTTP_AUTH_NEGOTIATE);
+}
+
+static int send_probe(http_stream *stream)
+{
+ http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
+ git_http_client *client = transport->http_client;
+ const char *probe = "0000";
+ size_t len = 4;
+ git_net_url url = GIT_NET_URL_INIT;
+ git_http_request request = {0};
+ git_http_response response = {0};
+ bool complete = false;
+ size_t step, steps = 1;
+ int error;
+
+ /* NTLM requires a full challenge/response */
+ if (transport->server.auth_schemetypes == GIT_HTTP_AUTH_NTLM)
+ steps = GIT_AUTH_STEPS_NTLM;
+
+ /*
+ * Send at most two requests: one without any authentication to see
+ * if we get prompted to authenticate. If we do, send a second one
+ * with the first authentication message. The final authentication
+ * message with the response will occur with the *actual* POST data.
+ */
+ for (step = 0; step < steps && !complete; step++) {
+ git_net_url_dispose(&url);
+ git_http_response_dispose(&response);
+
+ if ((error = generate_request(&url, &request, stream, len)) < 0 ||
+ (error = git_http_client_send_request(client, &request)) < 0 ||
+ (error = git_http_client_send_body(client, probe, len)) < 0 ||
+ (error = git_http_client_read_response(&response, client)) < 0 ||
+ (error = git_http_client_skip_body(client)) < 0 ||
+ (error = handle_response(&complete, stream, &response, true)) < 0)
+ goto done;
+ }
+
+done:
+ git_http_response_dispose(&response);
+ git_net_url_dispose(&url);
+ return error;
+}
+
+/*
+* Write to an HTTP transport - for the first invocation of this function
+* (ie, when stream->state == HTTP_STATE_NONE), we'll send a POST request
+* to the remote host. If we're sending chunked data, then subsequent calls
+* will write the additional data given in the buffer. If we're not chunking,
+* then the caller should have given us all the data in the original call.
+* The caller should call http_stream_read_response to get the result.
+*/
+static int http_stream_write(
+ git_smart_subtransport_stream *s,
+ const char *buffer,
+ size_t len)
+{
+ http_stream *stream = GIT_CONTAINER_OF(s, http_stream, parent);
+ http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
+ git_net_url url = GIT_NET_URL_INIT;
+ git_http_request request = {0};
+ git_http_response response = {0};
+ int error;
+
+ while (stream->state == HTTP_STATE_NONE &&
+ stream->replay_count < GIT_HTTP_REPLAY_MAX) {
+
+ git_net_url_dispose(&url);
+ git_http_response_dispose(&response);
+
+ /*
+ * If we're authenticating with a connection-based mechanism
+ * (NTLM, Kerberos), send a "probe" packet. Servers SHOULD
+ * authenticate an entire keep-alive connection, so ideally
+ * we should not need to authenticate but some servers do
+ * not support this. By sending a probe packet, we'll be
+ * able to follow up with a second POST using the actual
+ * data (and, in the degenerate case, the authentication
+ * header as well).
+ */
+ if (needs_probe(stream) && (error = send_probe(stream)) < 0)
+ goto done;
+
+ /* Send the regular POST request. */
+ if ((error = generate_request(&url, &request, stream, len)) < 0 ||
+ (error = git_http_client_send_request(
+ transport->http_client, &request)) < 0)
+ goto done;
+
+ if (request.expect_continue &&
+ git_http_client_has_response(transport->http_client)) {
+ bool complete;
+
+ /*
+ * If we got a response to an expect/continue, then
+ * it's something other than a 100 and we should
+ * deal with the response somehow.
+ */
+ if ((error = git_http_client_read_response(&response, transport->http_client)) < 0 ||
+ (error = handle_response(&complete, stream, &response, true)) < 0)
+ goto done;
+ } else {
+ stream->state = HTTP_STATE_SENDING_REQUEST;
+ }
+
+ stream->replay_count++;
+ }
+
+ if (stream->state == HTTP_STATE_NONE) {
+ git_error_set(GIT_ERROR_HTTP,
+ "too many redirects or authentication replays");
+ error = GIT_ERROR; /* not GIT_EAUTH because the exact cause is unclear */
+ goto done;
+ }
+
+ GIT_ASSERT(stream->state == HTTP_STATE_SENDING_REQUEST);
+
+ error = git_http_client_send_body(transport->http_client, buffer, len);
+
+done:
+ git_http_response_dispose(&response);
+ git_net_url_dispose(&url);
+ return error;
+}
+
+/*
+* Read from an HTTP transport after it has been written to. This is the
+* response from a POST request made by http_stream_write.
+*/
+static int http_stream_read_response(
+ git_smart_subtransport_stream *s,
+ char *buffer,
+ size_t buffer_size,
+ size_t *out_len)
+{
+ http_stream *stream = (http_stream *)s;
+ http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
+ git_http_client *client = transport->http_client;
+ git_http_response response = {0};
+ bool complete;
+ int error;
+
+ *out_len = 0;
+
+ if (stream->state == HTTP_STATE_SENDING_REQUEST) {
+ if ((error = git_http_client_read_response(&response, client)) < 0 ||
+ (error = handle_response(&complete, stream, &response, false)) < 0)
+ goto done;
+
+ GIT_ASSERT(complete);
+ stream->state = HTTP_STATE_RECEIVING_RESPONSE;
+ }
+
+ error = git_http_client_read_body(client, buffer, buffer_size);
+
+ if (error > 0) {
+ *out_len = error;
+ error = 0;
+ }
+
+done:
+ git_http_response_dispose(&response);
+ return error;
+}
+
+static void http_stream_free(git_smart_subtransport_stream *stream)
+{
+ http_stream *s = GIT_CONTAINER_OF(stream, http_stream, parent);
+ git__free(s);
+}
+
+static const http_service *select_service(git_smart_service_t action)
+{
+ switch (action) {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ return &upload_pack_ls_service;
+ case GIT_SERVICE_UPLOADPACK:
+ return &upload_pack_service;
+ case GIT_SERVICE_RECEIVEPACK_LS:
+ return &receive_pack_ls_service;
+ case GIT_SERVICE_RECEIVEPACK:
+ return &receive_pack_service;
+ }
+
+ return NULL;
+}
+
+static int http_action(
+ git_smart_subtransport_stream **out,
+ git_smart_subtransport *t,
+ const char *url,
+ git_smart_service_t action)
+{
+ http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent);
+ git_remote_connect_options *connect_opts = &transport->owner->connect_opts;
+ http_stream *stream;
+ const http_service *service;
+ int error;
+
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(t);
+
+ *out = NULL;
+
+ /*
+ * 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 (!git_net_url_valid(&transport->server.url) &&
+ (error = git_net_url_parse(&transport->server.url, url)) < 0)
+ return error;
+
+ if ((service = select_service(action)) == NULL) {
+ git_error_set(GIT_ERROR_HTTP, "invalid action");
+ return -1;
+ }
+
+ stream = git__calloc(sizeof(http_stream), 1);
+ GIT_ERROR_CHECK_ALLOC(stream);
+
+ if (!transport->http_client) {
+ git_http_client_options opts = {0};
+
+ opts.server_certificate_check_cb = connect_opts->callbacks.certificate_check;
+ opts.server_certificate_check_payload = connect_opts->callbacks.payload;
+ opts.proxy_certificate_check_cb = connect_opts->proxy_opts.certificate_check;
+ opts.proxy_certificate_check_payload = connect_opts->proxy_opts.payload;
+
+ if (git_http_client_new(&transport->http_client, &opts) < 0)
+ return -1;
+ }
+
+ stream->service = service;
+ stream->parent.subtransport = &transport->parent;
+
+ if (service->method == GIT_HTTP_METHOD_GET) {
+ stream->parent.read = http_stream_read;
+ } else {
+ stream->parent.write = http_stream_write;
+ stream->parent.read = http_stream_read_response;
+ }
+
+ stream->parent.free = http_stream_free;
+
+ *out = (git_smart_subtransport_stream *)stream;
+ return 0;
+}
+
+static int http_close(git_smart_subtransport *t)
+{
+ http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent);
+
+ free_cred(&transport->server.cred);
+ free_cred(&transport->proxy.cred);
+
+ transport->server.url_cred_presented = false;
+ transport->proxy.url_cred_presented = false;
+
+ git_net_url_dispose(&transport->server.url);
+ git_net_url_dispose(&transport->proxy.url);
+
+ return 0;
+}
+
+static void http_free(git_smart_subtransport *t)
+{
+ http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent);
+
+ git_http_client_free(transport->http_client);
+
+ http_close(t);
+ git__free(transport);
+}
+
+int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner, void *param)
+{
+ http_subtransport *transport;
+
+ GIT_UNUSED(param);
+
+ GIT_ASSERT_ARG(out);
+
+ transport = git__calloc(sizeof(http_subtransport), 1);
+ GIT_ERROR_CHECK_ALLOC(transport);
+
+ transport->owner = (transport_smart *)owner;
+ transport->parent.action = http_action;
+ transport->parent.close = http_close;
+ transport->parent.free = http_free;
+
+ *out = (git_smart_subtransport *) transport;
+ return 0;
+}
+
+#endif /* !GIT_WINHTTP */