]> git.proxmox.com Git - libgit2.git/blobdiff - src/transports/http.c
New upstream version 1.4.3+dfsg.1
[libgit2.git] / src / transports / http.c
index 7bb3374a0d75160e155080870ff80b07af56cf21..7db5582cab4b3d0e34af3d8eabab4400b8d247ca 100644 (file)
  * 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 "git2.h"
 #include "http_parser.h"
-#include "buffer.h"
+#include "net.h"
 #include "netops.h"
-#include "global.h"
 #include "remote.h"
 #include "smart.h"
 #include "auth.h"
+#include "http.h"
 #include "auth_negotiate.h"
-#include "tls_stream.h"
-#include "socket_stream.h"
-#include "curl_stream.h"
-
-git_http_auth_scheme auth_schemes[] = {
-       { GIT_AUTHTYPE_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, git_http_auth_negotiate },
-       { GIT_AUTHTYPE_BASIC, "Basic", GIT_CREDTYPE_USERPASS_PLAINTEXT, git_http_auth_basic },
-};
-
-static const char *upload_pack_service = "upload-pack";
-static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack";
-static const char *upload_pack_service_url = "/git-upload-pack";
-static const char *receive_pack_service = "receive-pack";
-static const char *receive_pack_ls_service_url = "/info/refs?service=git-receive-pack";
-static const char *receive_pack_service_url = "/git-receive-pack";
-static const char *get_verb = "GET";
-static const char *post_verb = "POST";
+#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;
 
-#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport)
-
-#define PARSE_ERROR_GENERIC    -1
-#define PARSE_ERROR_REPLAY     -2
-/** Look at the user field */
-#define PARSE_ERROR_EXT         -3
-
-#define CHUNK_SIZE     4096
-
-enum last_cb {
-       NONE,
-       FIELD,
-       VALUE
-};
+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 char *service;
-       const char *service_url;
-       char *redirect_url;
-       const char *verb;
-       char *chunk_buffer;
-       unsigned chunk_buffer_len;
-       unsigned sent_request : 1,
-               received_response : 1,
-               chunked : 1,
-               redirect_count : 3;
+       const http_service *service;
+       http_state state;
+       unsigned replay_count;
 } http_stream;
 
 typedef struct {
-       git_smart_subtransport parent;
-       transport_smart *owner;
-       git_stream *io;
-       gitno_connection_data connection_data;
-       bool connected;
-
-       /* Parser structures */
-       http_parser parser;
-       http_parser_settings settings;
-       gitno_buffer parse_buffer;
-       git_buf parse_header_name;
-       git_buf parse_header_value;
-       char parse_buffer_data[NETIO_BUFSIZE];
-       char *content_type;
-       char *location;
-       git_vector www_authenticate;
-       enum last_cb last_cb;
-       int parse_error;
-       int error;
-       unsigned parse_finished : 1;
+       git_net_url url;
 
-       /* Authentication */
-       git_cred *cred;
-       git_cred *url_cred;
-       git_vector auth_contexts;
-} http_subtransport;
+       git_credential *cred;
+       unsigned auth_schemetypes;
+       unsigned url_cred_presented : 1;
+} http_server;
 
 typedef struct {
-       http_stream *s;
-       http_subtransport *t;
+       git_smart_subtransport parent;
+       transport_smart *owner;
 
-       /* Target buffer details from read() */
-       char *buffer;
-       size_t buf_size;
-       size_t *bytes_read;
-} parser_context;
+       http_server server;
+       http_server proxy;
 
-static bool credtype_match(git_http_auth_scheme *scheme, void *data)
-{
-       unsigned int credtype = *(unsigned int *)data;
+       git_http_client *http_client;
+} http_subtransport;
 
-       return !!(scheme->credtypes & credtype);
-}
+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
+};
 
-static bool challenge_match(git_http_auth_scheme *scheme, void *data)
-{
-       const char *scheme_name = scheme->name;
-       const char *challenge = (const char *)data;
-       size_t scheme_len;
+#define SERVER_TYPE_REMOTE "remote"
+#define SERVER_TYPE_PROXY  "proxy"
 
-       scheme_len = strlen(scheme_name);
-       return (strncmp(challenge, scheme_name, scheme_len) == 0 &&
-               (challenge[scheme_len] == '\0' || challenge[scheme_len] == ' '));
-}
+#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport)
 
-static int auth_context_match(
-       git_http_auth_context **out,
-       http_subtransport *t,
-       bool (*scheme_match)(git_http_auth_scheme *scheme, void *data),
-       void *data)
+static int apply_url_credentials(
+       git_credential **cred,
+       unsigned int allowed_types,
+       const char *username,
+       const char *password)
 {
-       git_http_auth_scheme *scheme = NULL;
-       git_http_auth_context *context = NULL, *c;
-       size_t i;
-
-       *out = NULL;
-
-       for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) {
-               if (scheme_match(&auth_schemes[i], data)) {
-                       scheme = &auth_schemes[i];
-                       break;
-               }
-       }
+       GIT_ASSERT_ARG(username);
 
-       if (!scheme)
-               return 0;
-
-       /* See if authentication has already started for this scheme */
-       git_vector_foreach(&t->auth_contexts, i, c) {
-               if (c->type == scheme->type) {
-                       context = c;
-                       break;
-               }
-       }
+       if (!password)
+               password = "";
 
-       if (!context) {
-               if (scheme->init_context(&context, &t->connection_data) < 0)
-                       return -1;
-               else if (!context)
-                       return 0;
-               else if (git_vector_insert(&t->auth_contexts, context) < 0)
-                       return -1;
-       }
+       if (allowed_types & GIT_CREDENTIAL_USERPASS_PLAINTEXT)
+               return git_credential_userpass_plaintext_new(cred, username, password);
 
-       *out = context;
+       if ((allowed_types & GIT_CREDENTIAL_DEFAULT) && *username == '\0' && *password == '\0')
+               return git_credential_default_new(cred);
 
-       return 0;
+       return GIT_PASSTHROUGH;
 }
 
-static int apply_credentials(git_buf *buf, http_subtransport *t)
+GIT_INLINE(void) free_cred(git_credential **cred)
 {
-       git_cred *cred = t->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)
-                       return -1;
-
-               cred = t->url_cred;
+       if (*cred) {
+               git_credential_free(*cred);
+               (*cred) = NULL;
        }
-
-       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)
-               return -1;
-
-       return context->next_token(buf, context, cred);
 }
 
-static const char *user_agent(void)
-{
-       const char *custom = git_libgit2__user_agent();
-
-       if (custom)
-               return custom;
-
-       return "libgit2 " LIBGIT2_VERSION;
-}
-
-static int gen_request(
-       git_buf *buf,
-       http_stream *s,
-       size_t content_length)
+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)
 {
-       http_subtransport *t = OWNING_SUBTRANSPORT(s);
-       const char *path = t->connection_data.path ? t->connection_data.path : "/";
-       size_t i;
+       int error = 1;
 
-       git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, path, s->service_url);
+       if (server->cred)
+               free_cred(&server->cred);
 
-       git_buf_printf(buf, "User-Agent: git/1.0 (%s)\r\n", user_agent());
-       git_buf_printf(buf, "Host: %s\r\n", t->connection_data.host);
+       /* 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;
 
-       if (s->chunked || content_length > 0) {
-               git_buf_printf(buf, "Accept: application/x-git-%s-result\r\n", s->service);
-               git_buf_printf(buf, "Content-Type: application/x-git-%s-request\r\n", s->service);
+               /* treat GIT_PASSTHROUGH as if callback isn't set */
+               if (error == GIT_PASSTHROUGH)
+                       error = 1;
+       }
 
-               if (s->chunked)
-                       git_buf_puts(buf, "Transfer-Encoding: chunked\r\n");
-               else
-                       git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n", content_length);
-       } else
-               git_buf_puts(buf, "Accept: */*\r\n");
+       if (error > 0 && callback) {
+               error = callback(&server->cred, url, server->url.username, allowed_credtypes, callback_payload);
 
-       for (i = 0; i < t->owner->custom_headers.count; i++) {
-               if (t->owner->custom_headers.strings[i])
-                       git_buf_printf(buf, "%s\r\n", t->owner->custom_headers.strings[i]);
+               /* treat GIT_PASSTHROUGH as if callback isn't set */
+               if (error == GIT_PASSTHROUGH)
+                       error = 1;
        }
 
-       /* Apply credentials to the request */
-       if (apply_credentials(buf, t) < 0)
-               return -1;
-
-       git_buf_puts(buf, "\r\n");
+       if (error > 0) {
+               git_error_set(GIT_ERROR_HTTP, "%s authentication required but no callback set", server_type);
+               error = GIT_EAUTH;
+       }
 
-       if (git_buf_oom(buf))
-               return -1;
+       if (!error)
+               server->auth_schemetypes = allowed_schemetypes;
 
-       return 0;
+       return error;
 }
 
-static int parse_authenticate_response(
-       git_vector *www_authenticate,
-       http_subtransport *t,
-       int *allowed_types)
+GIT_INLINE(int) handle_remote_auth(
+       http_stream *stream,
+       git_http_response *response)
 {
-       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)
-                       return -1;
-               else if (!context)
-                       continue;
-
-               if (context->set_challenge &&
-                       context->set_challenge(context, challenge) < 0)
-                       return -1;
-
-               *allowed_types |= context->credtypes;
-       }
-
-       return 0;
+       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);
 }
 
-static int on_header_ready(http_subtransport *t)
+GIT_INLINE(int) handle_proxy_auth(
+       http_stream *stream,
+       git_http_response *response)
 {
-       git_buf *name = &t->parse_header_name;
-       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);
-               }
-       }
-       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);
-       }
-       else if (!strcasecmp("Location", git_buf_cstr(name))) {
-               if (!t->location) {
-                       t->location = git__strdup(git_buf_cstr(value));
-                       GITERR_CHECK_ALLOC(t->location);
-               }
-       }
-
-       return 0;
+       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 int on_header_field(http_parser *parser, const char *str, size_t len)
+static bool allow_redirect(http_stream *stream)
 {
-       parser_context *ctx = (parser_context *) parser->data;
-       http_subtransport *t = ctx->t;
-
-       /* 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;
-
-       if (NONE == t->last_cb || VALUE == t->last_cb)
-               git_buf_clear(&t->parse_header_name);
+       http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
 
-       if (git_buf_put(&t->parse_header_name, str, len) < 0)
-               return t->parse_error = PARSE_ERROR_GENERIC;
-
-       t->last_cb = FIELD;
-       return 0;
+       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 on_header_value(http_parser *parser, const char *str, size_t len)
+static int handle_response(
+       bool *complete,
+       http_stream *stream,
+       git_http_response *response,
+       bool allow_replay)
 {
-       parser_context *ctx = (parser_context *) parser->data;
-       http_subtransport *t = ctx->t;
-
-       assert(NONE != t->last_cb);
-
-       if (FIELD == t->last_cb)
-               git_buf_clear(&t->parse_header_value);
+       http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
+       int error;
 
-       if (git_buf_put(&t->parse_header_value, str, len) < 0)
-               return t->parse_error = PARSE_ERROR_GENERIC;
+       *complete = false;
 
-       t->last_cb = VALUE;
-       return 0;
-}
-
-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;
-
-       /* 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 which may be a 401 (authentication
-        * is not complete) or a 200 (simply informing us that auth *is*
-        * complete.)
-        */
-       if (parse_authenticate_response(&t->www_authenticate, t,
-                       &allowed_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);
-
-                               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;
-                               }
-                       }
-               }
-
-               if (no_callback) {
-                       giterr_set(GITERR_NET, "authentication required but no callback set");
-                       return t->parse_error = PARSE_ERROR_GENERIC;
+       if (allow_replay && git_http_response_is_redirect(response)) {
+               if (!response->location) {
+                       git_error_set(GIT_ERROR_HTTP, "redirect without location");
+                       return -1;
                }
-       }
 
-       /* Check for a redirect.
-        * Right now we only permit a redirect to the same hostname. */
-       if ((parser->status_code == 301 ||
-            parser->status_code == 302 ||
-            (parser->status_code == 303 && get_verb == s->verb) ||
-            parser->status_code == 307) &&
-           t->location) {
-
-               if (s->redirect_count >= 7) {
-                       giterr_set(GITERR_NET, "Too many redirects");
-                       return t->parse_error = PARSE_ERROR_GENERIC;
+               if (git_net_url_apply_redirect(&transport->server.url, response->location, allow_redirect(stream), stream->service->url) < 0) {
+                       return -1;
                }
 
-               if (gitno_connection_data_from_url(&t->connection_data, 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
-                * ownership of the memory. */
-               if (s->redirect_url)
-                       git__free(s->redirect_url);
+               return 0;
+       } else if (git_http_response_is_redirect(response)) {
+               git_error_set(GIT_ERROR_HTTP, "unexpected redirect");
+               return -1;
+       }
 
-               s->redirect_url = t->location;
-               t->location = NULL;
+       /* 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;
 
-               t->connected = 0;
-               s->redirect_count++;
+               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;
 
-               t->parse_error = PARSE_ERROR_REPLAY;
-               return 0;
+               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;
        }
 
-       /* Check for a 200 HTTP status code. */
-       if (parser->status_code != 200) {
-               giterr_set(GITERR_NET,
-                       "Unexpected HTTP status code: %d",
-                       parser->status_code);
-               return t->parse_error = PARSE_ERROR_GENERIC;
+       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 (!t->content_type) {
-               giterr_set(GITERR_NET, "No Content-Type header in response");
-               return t->parse_error = PARSE_ERROR_GENERIC;
+       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 (get_verb == s->verb)
-               git_buf_printf(&buf,
-                       "application/x-git-%s-advertisement",
-                       ctx->s->service);
-       else
-               git_buf_printf(&buf,
-                       "application/x-git-%s-result",
-                       ctx->s->service);
-
-       if (git_buf_oom(&buf))
-               return t->parse_error = PARSE_ERROR_GENERIC;
-
-       if (strcmp(t->content_type, git_buf_cstr(&buf))) {
-               git_buf_free(&buf);
-               giterr_set(GITERR_NET,
-                       "Invalid Content-Type: %s",
-                       t->content_type);
-               return t->parse_error = PARSE_ERROR_GENERIC;
-       }
-
-       git_buf_free(&buf);
-
-       return 0;
-}
-
-static int on_message_complete(http_parser *parser)
-{
-       parser_context *ctx = (parser_context *) parser->data;
-       http_subtransport *t = ctx->t;
-
-       t->parse_finished = 1;
-
-       return 0;
-}
-
-static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
-{
-       parser_context *ctx = (parser_context *) parser->data;
-       http_subtransport *t = ctx->t;
-
-       /* If our goal is to replay the request (either an auth failure or
-        * a redirect) then don't bother buffering since we're ignoring the
-        * content anyway.
-        */
-       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 (strcmp(response->content_type, stream->service->response_type) != 0) {
+               git_error_set(GIT_ERROR_HTTP, "invalid content-type: '%s'", response->content_type);
+               return -1;
        }
 
-       memcpy(ctx->buffer, str, len);
-       *(ctx->bytes_read) += len;
-       ctx->buffer += len;
-       ctx->buf_size -= len;
-
+       *complete = true;
+       stream->state = HTTP_STATE_RECEIVING_RESPONSE;
        return 0;
 }
 
-static void clear_parser_state(http_subtransport *t)
-{
-       http_parser_init(&t->parser, HTTP_RESPONSE);
-       gitno_buffer_setup_fromstream(t->io,
-               &t->parse_buffer,
-               t->parse_buffer_data,
-               sizeof(t->parse_buffer_data));
-
-       t->last_cb = NONE;
-       t->parse_error = 0;
-       t->parse_finished = 0;
-
-       git_buf_free(&t->parse_header_name);
-       git_buf_init(&t->parse_header_name, 0);
-
-       git_buf_free(&t->parse_header_value);
-       git_buf_init(&t->parse_header_value, 0);
-
-       git__free(t->content_type);
-       t->content_type = NULL;
-
-       git__free(t->location);
-       t->location = NULL;
-
-       git_vector_free_deep(&t->www_authenticate);
-}
-
-static int write_chunk(git_stream *io, const char *buffer, size_t len)
+static int lookup_proxy(
+       bool *out_use,
+       http_subtransport *transport)
 {
-       git_buf buf = GIT_BUF_INIT;
-
-       /* Chunk header */
-       git_buf_printf(&buf, "%" PRIxZ "\r\n", len);
-
-       if (git_buf_oom(&buf))
-               return -1;
+       git_remote_connect_options *connect_opts = &transport->owner->connect_opts;
+       const char *proxy;
+       git_remote *remote;
+       char *config = NULL;
+       int error = 0;
 
-       if (git_stream_write(io, buf.ptr, buf.size, 0) < 0) {
-               git_buf_free(&buf);
-               return -1;
-       }
-
-       git_buf_free(&buf);
-
-       /* Chunk body */
-       if (len > 0 && git_stream_write(io, buffer, len, 0) < 0)
-               return -1;
+       *out_use = false;
+       git_net_url_dispose(&transport->proxy.url);
 
-       /* Chunk footer */
-       if (git_stream_write(io, "\r\n", 2, 0) < 0)
-               return -1;
+       switch (connect_opts->proxy_opts.type) {
+       case GIT_PROXY_SPECIFIED:
+               proxy = connect_opts->proxy_opts.url;
+               break;
 
-       return 0;
-}
+       case GIT_PROXY_AUTO:
+               remote = transport->owner->owner;
 
-static int apply_proxy_config(http_subtransport *t)
-{
-       int error;
-       git_proxy_t proxy_type;
+               error = git_remote__http_proxy(&config, remote, &transport->server.url);
 
-       if (!git_stream_supports_proxy(t->io))
-               return 0;
+               if (error || !config)
+                       goto done;
 
-       proxy_type = t->owner->proxy.type;
+               proxy = config;
+               break;
 
-       if (proxy_type == GIT_PROXY_NONE)
+       default:
                return 0;
+       }
 
-       if (proxy_type == GIT_PROXY_AUTO) {
-               char *url;
-               git_proxy_options opts = GIT_PROXY_OPTIONS_INIT;
-
-               if ((error = git_remote__get_http_proxy(t->owner->owner, !!t->connection_data.use_ssl, &url)) < 0)
-                       return error;
-
-               opts.type = GIT_PROXY_SPECIFIED;
-               opts.url = url;
-               error = git_stream_set_proxy(t->io, &opts);
-               git__free(url);
+       if (!proxy ||
+           (error = git_net_url_parse(&transport->proxy.url, proxy)) < 0)
+               goto done;
 
-               return error;
-       }
+       *out_use = true;
 
-       return git_stream_set_proxy(t->io, &t->owner->proxy);
+done:
+       git__free(config);
+       return error;
 }
 
-static int http_connect(http_subtransport *t)
+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 (t->connected &&
-               http_should_keep_alive(&t->parser) &&
-               t->parse_finished)
-               return 0;
-
-       if (t->io) {
-               git_stream_close(t->io);
-               git_stream_free(t->io);
-               t->io = NULL;
-       }
-
-       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
-       }
-
-       if (error < 0)
+       if ((error = git_net_url_joinpath(url,
+               &transport->server.url, stream->service->url)) < 0 ||
+           (error = lookup_proxy(&use_proxy, transport)) < 0)
                return error;
 
-       GITERR_CHECK_VERSION(t->io, GIT_STREAM_VERSION, "git_stream");
+       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;
 
-       apply_proxy_config(t);
-
-       error = git_stream_connect(t->io);
-
-#if defined(GIT_OPENSSL) || defined(GIT_SECURE_TRANSPORT) || defined(GIT_CURL)
-       if ((!error || error == GIT_ECERTIFICATE) && t->owner->certificate_check_cb != NULL &&
-           git_stream_is_encrypted(t->io)) {
-               git_cert *cert;
-               int is_valid;
-
-               if ((error = git_stream_certificate(&cert, t->io)) < 0)
-                       return error;
-
-               giterr_clear();
-               is_valid = error != GIT_ECERTIFICATE;
-               error = t->owner->certificate_check_cb(cert, is_valid, t->connection_data.host, t->owner->message_cb_payload);
-
-               if (error < 0) {
-                       if (!giterr_last())
-                               giterr_set(GITERR_NET, "user cancelled certificate check");
-
-                       return error;
-               }
+       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;
        }
-#endif
-       if (error < 0)
-               return error;
 
-       t->connected = 1;
        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 *stream,
+       git_smart_subtransport_stream *s,
        char *buffer,
-       size_t buf_size,
-       size_t *bytes_read)
+       size_t buffer_size,
+       size_t *out_len)
 {
-       http_stream *s = (http_stream *)stream;
-       http_subtransport *t = OWNING_SUBTRANSPORT(s);
-       parser_context ctx;
-       size_t bytes_parsed;
-
-replay:
-       *bytes_read = 0;
-
-       assert(t->connected);
-
-       if (!s->sent_request) {
-               git_buf request = GIT_BUF_INIT;
-
-               clear_parser_state(t);
-
-               if (gen_request(&request, s, 0) < 0)
-                       return -1;
-
-               if (git_stream_write(t->io, request.ptr, request.size, 0) < 0) {
-                       git_buf_free(&request);
-                       return -1;
-               }
+       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;
 
-               git_buf_free(&request);
+       *out_len = 0;
 
-               s->sent_request = 1;
+       if (stream->state == HTTP_STATE_NONE) {
+               stream->state = HTTP_STATE_SENDING_REQUEST;
+               stream->replay_count = 0;
        }
 
-       if (!s->received_response) {
-               if (s->chunked) {
-                       assert(s->verb == post_verb);
-
-                       /* Flush, if necessary */
-                       if (s->chunk_buffer_len > 0 &&
-                               write_chunk(t->io, 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)
-                               return -1;
-               }
+       /*
+        * 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;
 
-               s->received_response = 1;
+               stream->replay_count++;
        }
 
-       while (!*bytes_read && !t->parse_finished) {
-               size_t data_offset;
-               int error;
-
-               /*
-                * Make the parse_buffer think it's as full of data as
-                * the buffer, so it won't try to recv more data than
-                * we can put into it.
-                *
-                * data_offset is the actual data offset from which we
-                * should tell the parser to start reading.
-                */
-               if (buf_size >= t->parse_buffer.len) {
-                       t->parse_buffer.offset = 0;
-               } else {
-                       t->parse_buffer.offset = t->parse_buffer.len - buf_size;
-               }
-
-               data_offset = t->parse_buffer.offset;
-
-               if (gitno_recv(&t->parse_buffer) < 0)
-                       return -1;
-
-               /* This call to http_parser_execute will result in invocations of the
-                * on_* family of callbacks. The most interesting of these is
-                * on_body_fill_buffer, which is called when data is ready to be copied
-                * into the target buffer. We need to marshal the buffer, buf_size, and
-                * bytes_read parameters to this callback. */
-               ctx.t = t;
-               ctx.s = s;
-               ctx.buffer = buffer;
-               ctx.buf_size = buf_size;
-               ctx.bytes_read = bytes_read;
+       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;
+       }
 
-               /* Set the context, call the parser, then unset the context. */
-               t->parser.data = &ctx;
+       GIT_ASSERT(stream->state == HTTP_STATE_RECEIVING_RESPONSE);
 
-               bytes_parsed = http_parser_execute(&t->parser,
-                       &t->settings,
-                       t->parse_buffer.data + data_offset,
-                       t->parse_buffer.offset - data_offset);
+       error = git_http_client_read_body(transport->http_client, buffer, buffer_size);
 
-               t->parser.data = NULL;
+       if (error > 0) {
+               *out_len = error;
+               error = 0;
+       }
 
-               /* If there was a handled authentication failure, then parse_error
-                * will have signaled us that we should replay the request. */
-               if (PARSE_ERROR_REPLAY == t->parse_error) {
-                       s->sent_request = 0;
+done:
+       git_net_url_dispose(&url);
+       git_net_url_dispose(&proxy_url);
+       git_http_response_dispose(&response);
 
-                       if ((error = http_connect(t)) < 0)
-                               return error;
+       return error;
+}
 
-                       goto replay;
-               }
+static bool needs_probe(http_stream *stream)
+{
+       http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
 
-               if (t->parse_error == PARSE_ERROR_EXT) {
-                       return t->error;
-               }
+       return (transport->server.auth_schemetypes == GIT_HTTP_AUTH_NTLM ||
+               transport->server.auth_schemetypes == GIT_HTTP_AUTH_NEGOTIATE);
+}
 
-               if (t->parse_error < 0)
-                       return -1;
+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;
 
-               if (bytes_parsed != t->parse_buffer.offset - data_offset) {
-                       giterr_set(GITERR_NET,
-                               "HTTP parser error: %s",
-                               http_errno_description((enum http_errno)t->parser.http_errno));
-                       return -1;
-               }
-       }
+       /* NTLM requires a full challenge/response */
+       if (transport->server.auth_schemetypes == GIT_HTTP_AUTH_NTLM)
+               steps = GIT_AUTH_STEPS_NTLM;
 
-       return 0;
+       /*
+        * 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;
 }
 
-static int http_stream_write_chunked(
-       git_smart_subtransport_stream *stream,
+/*
+* 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 *s = (http_stream *)stream;
-       http_subtransport *t = OWNING_SUBTRANSPORT(s);
-
-       assert(t->connected);
-
-       /* Send the request, if necessary */
-       if (!s->sent_request) {
-               git_buf request = GIT_BUF_INIT;
+       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;
 
-               clear_parser_state(t);
+       while (stream->state == HTTP_STATE_NONE &&
+              stream->replay_count < GIT_HTTP_REPLAY_MAX) {
 
-               if (gen_request(&request, s, 0) < 0)
-                       return -1;
+               git_net_url_dispose(&url);
+               git_http_response_dispose(&response);
 
-               if (git_stream_write(t->io, request.ptr, request.size, 0) < 0) {
-                       git_buf_free(&request);
-                       return -1;
+               /*
+                * 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;
                }
 
-               git_buf_free(&request);
-
-               s->sent_request = 1;
+               stream->replay_count++;
        }
 
-       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)
-                               return -1;
-
-                       s->chunk_buffer_len = 0;
-               }
-
-               /* Write chunk directly */
-               if (write_chunk(t->io, buffer, len) < 0)
-                       return -1;
+       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;
        }
-       else {
-               /* Append as much to the buffer as we can */
-               int count = min(CHUNK_SIZE - s->chunk_buffer_len, len);
-
-               if (!s->chunk_buffer)
-                       s->chunk_buffer = git__malloc(CHUNK_SIZE);
 
-               memcpy(s->chunk_buffer + s->chunk_buffer_len, buffer, count);
-               s->chunk_buffer_len += count;
-               buffer += count;
-               len -= count;
+       GIT_ASSERT(stream->state == HTTP_STATE_SENDING_REQUEST);
 
-               /* 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)
-                               return -1;
+       error = git_http_client_send_body(transport->http_client, buffer, len);
 
-                       s->chunk_buffer_len = 0;
-
-                       if (len > 0) {
-                               memcpy(s->chunk_buffer, buffer, len);
-                               s->chunk_buffer_len = len;
-                       }
-               }
-       }
-
-       return 0;
+done:
+       git_http_response_dispose(&response);
+       git_net_url_dispose(&url);
+       return error;
 }
 
-static int http_stream_write_single(
-       git_smart_subtransport_stream *stream,
-       const char *buffer,
-       size_t len)
+/*
+* 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 *s = (http_stream *)stream;
-       http_subtransport *t = OWNING_SUBTRANSPORT(s);
-       git_buf request = GIT_BUF_INIT;
-
-       assert(t->connected);
-
-       if (s->sent_request) {
-               giterr_set(GITERR_NET, "Subtransport configured for only one write");
-               return -1;
-       }
+       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;
 
-       clear_parser_state(t);
+       *out_len = 0;
 
-       if (gen_request(&request, s, len) < 0)
-               return -1;
+       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;
 
-       if (git_stream_write(t->io, request.ptr, request.size, 0) < 0)
-               goto on_error;
-
-       if (len && git_stream_write(t->io, buffer, len, 0) < 0)
-               goto on_error;
+               GIT_ASSERT(complete);
+               stream->state = HTTP_STATE_RECEIVING_RESPONSE;
+       }
 
-       git_buf_free(&request);
-       s->sent_request = 1;
+       error = git_http_client_read_body(client, buffer, buffer_size);
 
-       return 0;
+       if (error > 0) {
+               *out_len = error;
+               error = 0;
+       }
 
-on_error:
-       git_buf_free(&request);
-       return -1;
+done:
+       git_http_response_dispose(&response);
+       return error;
 }
 
 static void http_stream_free(git_smart_subtransport_stream *stream)
 {
-       http_stream *s = (http_stream *)stream;
-
-       if (s->chunk_buffer)
-               git__free(s->chunk_buffer);
-
-       if (s->redirect_url)
-               git__free(s->redirect_url);
-
+       http_stream *s = GIT_CONTAINER_OF(stream, http_stream, parent);
        git__free(s);
 }
 
-static int http_stream_alloc(http_subtransport *t,
-       git_smart_subtransport_stream **stream)
+static const http_service *select_service(git_smart_service_t action)
 {
-       http_stream *s;
-
-       if (!stream)
-               return -1;
-
-       s = git__calloc(sizeof(http_stream), 1);
-       GITERR_CHECK_ALLOC(s);
-
-       s->parent.subtransport = &t->parent;
-       s->parent.read = http_stream_read;
-       s->parent.write = http_stream_write_single;
-       s->parent.free = http_stream_free;
-
-       *stream = (git_smart_subtransport_stream *)s;
-       return 0;
-}
-
-static int http_uploadpack_ls(
-       http_subtransport *t,
-       git_smart_subtransport_stream **stream)
-{
-       http_stream *s;
-
-       if (http_stream_alloc(t, stream) < 0)
-               return -1;
-
-       s = (http_stream *)*stream;
-
-       s->service = upload_pack_service;
-       s->service_url = upload_pack_ls_service_url;
-       s->verb = get_verb;
+       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 0;
+       return NULL;
 }
 
-static int http_uploadpack(
-       http_subtransport *t,
-       git_smart_subtransport_stream **stream)
+static int http_action(
+       git_smart_subtransport_stream **out,
+       git_smart_subtransport *t,
+       const char *url,
+       git_smart_service_t action)
 {
-       http_stream *s;
-
-       if (http_stream_alloc(t, stream) < 0)
-               return -1;
-
-       s = (http_stream *)*stream;
+       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;
 
-       s->service = upload_pack_service;
-       s->service_url = upload_pack_service_url;
-       s->verb = post_verb;
+       GIT_ASSERT_ARG(out);
+       GIT_ASSERT_ARG(t);
 
-       return 0;
-}
+       *out = NULL;
 
-static int http_receivepack_ls(
-       http_subtransport *t,
-       git_smart_subtransport_stream **stream)
-{
-       http_stream *s;
+       /*
+        * 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 (http_stream_alloc(t, stream) < 0)
+       if ((service = select_service(action)) == NULL) {
+               git_error_set(GIT_ERROR_HTTP, "invalid action");
                return -1;
+       }
 
-       s = (http_stream *)*stream;
-
-       s->service = receive_pack_service;
-       s->service_url = receive_pack_ls_service_url;
-       s->verb = get_verb;
+       stream = git__calloc(sizeof(http_stream), 1);
+       GIT_ERROR_CHECK_ALLOC(stream);
 
-       return 0;
-}
+       if (!transport->http_client) {
+               git_http_client_options opts = {0};
 
-static int http_receivepack(
-       http_subtransport *t,
-       git_smart_subtransport_stream **stream)
-{
-       http_stream *s;
+               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 (http_stream_alloc(t, stream) < 0)
-               return -1;
+               if (git_http_client_new(&transport->http_client, &opts) < 0)
+                       return -1;
+       }
 
-       s = (http_stream *)*stream;
+       stream->service = service;
+       stream->parent.subtransport = &transport->parent;
 
-       /* Use Transfer-Encoding: chunked for this request */
-       s->chunked = 1;
-       s->parent.write = http_stream_write_chunked;
+       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;
+       }
 
-       s->service = receive_pack_service;
-       s->service_url = receive_pack_service_url;
-       s->verb = post_verb;
+       stream->parent.free = http_stream_free;
 
+       *out = (git_smart_subtransport_stream *)stream;
        return 0;
 }
 
-static int http_action(
-       git_smart_subtransport_stream **stream,
-       git_smart_subtransport *subtransport,
-       const char *url,
-       git_smart_service_t action)
-{
-       http_subtransport *t = (http_subtransport *)subtransport;
-       int ret;
-
-       if (!stream)
-               return -1;
-
-       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)
-               return ret;
-
-       if ((ret = http_connect(t)) < 0)
-               return ret;
-
-       switch (action) {
-       case GIT_SERVICE_UPLOADPACK_LS:
-               return http_uploadpack_ls(t, stream);
-
-       case GIT_SERVICE_UPLOADPACK:
-               return http_uploadpack(t, stream);
-
-       case GIT_SERVICE_RECEIVEPACK_LS:
-               return http_receivepack_ls(t, stream);
-
-       case GIT_SERVICE_RECEIVEPACK:
-               return http_receivepack(t, stream);
-       }
-
-       *stream = NULL;
-       return -1;
-}
-
-static int http_close(git_smart_subtransport *subtransport)
+static int http_close(git_smart_subtransport *t)
 {
-       http_subtransport *t = (http_subtransport *) subtransport;
-       git_http_auth_context *context;
-       size_t i;
+       http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent);
 
-       clear_parser_state(t);
+       free_cred(&transport->server.cred);
+       free_cred(&transport->proxy.cred);
 
-       if (t->io) {
-               git_stream_close(t->io);
-               git_stream_free(t->io);
-               t->io = NULL;
-       }
-
-       if (t->cred) {
-               t->cred->free(t->cred);
-               t->cred = NULL;
-       }
-
-       if (t->url_cred) {
-               t->url_cred->free(t->url_cred);
-               t->url_cred = NULL;
-       }
+       transport->server.url_cred_presented = false;
+       transport->proxy.url_cred_presented = false;
 
-       git_vector_foreach(&t->auth_contexts, i, context) {
-               if (context->free)
-                       context->free(context);
-       }
-
-       git_vector_clear(&t->auth_contexts);
-
-       gitno_connection_data_free_ptrs(&t->connection_data);
-       memset(&t->connection_data, 0x0, sizeof(gitno_connection_data));
+       git_net_url_dispose(&transport->server.url);
+       git_net_url_dispose(&transport->proxy.url);
 
        return 0;
 }
 
-static void http_free(git_smart_subtransport *subtransport)
+static void http_free(git_smart_subtransport *t)
 {
-       http_subtransport *t = (http_subtransport *) subtransport;
+       http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent);
 
-       http_close(subtransport);
+       git_http_client_free(transport->http_client);
 
-       git_vector_free(&t->auth_contexts);
-       git__free(t);
+       http_close(t);
+       git__free(transport);
 }
 
 int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner, void *param)
 {
-       http_subtransport *t;
+       http_subtransport *transport;
 
        GIT_UNUSED(param);
 
-       if (!out)
-               return -1;
-
-       t = git__calloc(sizeof(http_subtransport), 1);
-       GITERR_CHECK_ALLOC(t);
+       GIT_ASSERT_ARG(out);
 
-       t->owner = (transport_smart *)owner;
-       t->parent.action = http_action;
-       t->parent.close = http_close;
-       t->parent.free = http_free;
+       transport = git__calloc(sizeof(http_subtransport), 1);
+       GIT_ERROR_CHECK_ALLOC(transport);
 
-       t->settings.on_header_field = on_header_field;
-       t->settings.on_header_value = on_header_value;
-       t->settings.on_headers_complete = on_headers_complete;
-       t->settings.on_body = on_body_fill_buffer;
-       t->settings.on_message_complete = on_message_complete;
+       transport->owner = (transport_smart *)owner;
+       transport->parent.action = http_action;
+       transport->parent.close = http_close;
+       transport->parent.free = http_free;
 
-       *out = (git_smart_subtransport *) t;
+       *out = (git_smart_subtransport *) transport;
        return 0;
 }