summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2019-12-22 14:12:24 -0800
committerEdward Thomson <ethomson@edwardthomson.com>2020-01-24 10:39:51 -0600
commitb9c5b15a7958ab4ecb83504a6d858efd18610297 (patch)
treef3cb851fdf271c298dadc648324e8979fc1fbbca
parentbf55facf15e5b6897ff7305162a77d92089f6c82 (diff)
downloadlibgit2-b9c5b15a7958ab4ecb83504a6d858efd18610297.tar.gz
http: use the new httpclient
Untangle the notion of the http transport from the actual http implementation. The http transport now uses the httpclient.
-rw-r--r--src/transports/http.c1780
-rw-r--r--src/transports/http.h1
2 files changed, 407 insertions, 1374 deletions
diff --git a/src/transports/http.c b/src/transports/http.c
index 9e35f23c2..c535f2a35 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -22,406 +22,82 @@
#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"
bool git_http__expect_continue = false;
-git_http_auth_scheme auth_schemes[] = {
- { GIT_HTTP_AUTH_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, git_http_auth_negotiate },
- { GIT_HTTP_AUTH_NTLM, "NTLM", GIT_CREDTYPE_USERPASS_PLAINTEXT, git_http_auth_ntlm },
- { GIT_HTTP_AUTH_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";
-
-#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)
+typedef enum {
+ HTTP_STATE_NONE = 0,
+ HTTP_STATE_SENDING_REQUEST,
+ HTTP_STATE_RECEIVING_RESPONSE,
+ HTTP_STATE_DONE
+} http_state;
-#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 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;
+ const http_service *service;
+ http_state state;
+ unsigned replay_count;
} http_stream;
typedef struct {
git_net_url url;
- git_stream *stream;
-
- git_http_auth_t authtypes;
- git_credtype_t credtypes;
git_cred *cred;
- unsigned url_cred_presented : 1,
- authenticated : 1;
- git_http_authtype_t prior_authtype;
-
- git_vector auth_challenges;
- git_http_auth_context *auth_context;
+ unsigned auth_schemetypes;
+ unsigned url_cred_presented : 1;
} http_server;
typedef struct {
git_smart_subtransport parent;
transport_smart *owner;
- 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;
- gitno_buffer parse_buffer;
- git_buf parse_header_name;
- git_buf parse_header_value;
- char parse_buffer_data[NETIO_BUFSIZE];
- char *content_type;
- char *content_length;
- char *location;
- enum last_cb last_cb;
- int parse_error;
- int error;
- unsigned request_count;
- unsigned parse_finished : 1,
- keepalive : 1,
- replay_count : 4;
-} http_subtransport;
-
-typedef struct {
- http_stream *s;
- http_subtransport *t;
-
- /* Target buffer details from read() */
- char *buffer;
- size_t buf_size;
- size_t *bytes_read;
-} parser_context;
-
-static git_http_auth_scheme *scheme_for_challenge(
- const char *challenge,
- git_cred *cred)
-{
- git_http_auth_scheme *scheme = NULL;
- size_t i;
-
- for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) {
- const char *scheme_name = auth_schemes[i].name;
- const git_credtype_t scheme_types = auth_schemes[i].credtypes;
- size_t scheme_len;
-
- scheme_len = strlen(scheme_name);
-
- if ((!cred || (cred->credtype & scheme_types)) &&
- strncasecmp(challenge, scheme_name, scheme_len) == 0 &&
- (challenge[scheme_len] == '\0' || challenge[scheme_len] == ' ')) {
- scheme = &auth_schemes[i];
- break;
- }
- }
-
- return scheme;
-}
-
-static int apply_credentials(
- git_buf *buf,
- http_server *server,
- const char *header_name)
-{
- git_buf token = GIT_BUF_INIT;
- int error = 0;
- if (!server->auth_context)
- goto done;
-
- if ((error = server->auth_context->next_token(&token, server->auth_context, server->cred)) < 0)
- goto done;
-
- error = git_buf_printf(buf, "%s: %s\r\n", header_name, token.ptr);
-
-done:
- git_buf_dispose(&token);
- return error;
-}
-
-static int gen_request(
- git_buf *buf,
- http_stream *s,
- size_t content_length)
-{
- http_subtransport *t = OWNING_SUBTRANSPORT(s);
- const char *path = t->server.url.path ? t->server.url.path : "/";
- const char *service_url = s->service_url;
- size_t i;
- /* If path already ends in /, remove the leading slash from service_url */
- if ((git__suffixcmp(path, "/") == 0) && (git__prefixcmp(service_url, "/") == 0))
- 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.scheme,
- t->server.url.host,
- t->server.url.port,
- path, service_url);
- else
- git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n",
- s->verb, path, 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->server.url.host);
-
- if (!git_net_url_is_default_port(&t->server.url))
- git_buf_printf(buf, ":%s", t->server.url.port);
-
- git_buf_puts(buf, "\r\n");
-
- 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);
-
- 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");
-
- 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]);
- }
-
- /* 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");
-
- if (git_buf_oom(buf))
- return -1;
-
- return 0;
-}
-
-static int set_authentication_challenge(http_server *server)
-{
- const char *challenge;
-
- if (git_vector_length(&server->auth_challenges) > 1) {
- git_error_set(GIT_ERROR_NET, "received multiple authentication challenges");
- return -1;
- }
-
- challenge = git_vector_get(&server->auth_challenges, 0);
-
- if (server->auth_context->set_challenge)
- return server->auth_context->set_challenge(server->auth_context, challenge);
- else
- return 0;
-}
-
-static int set_authentication_types(http_server *server)
-{
- git_http_auth_scheme *scheme;
- char *challenge;
- size_t i;
-
- git_vector_foreach(&server->auth_challenges, i, challenge) {
- if ((scheme = scheme_for_challenge(challenge, NULL)) != NULL) {
- server->authtypes |= scheme->type;
- server->credtypes |= scheme->credtypes;
- }
- }
-
- return 0;
-}
-
-static bool auth_context_complete(http_server *server)
-{
- /* If there's no is_complete function, we're always complete */
- if (!server->auth_context->is_complete)
- return true;
-
- if (server->auth_context->is_complete(server->auth_context))
- return true;
-
- return false;
-}
-
-static void free_auth_context(http_server *server)
-{
- if (!server->auth_context)
- return;
-
- if (server->auth_context->free)
- server->auth_context->free(server->auth_context);
-
- server->auth_context = NULL;
-}
-
-static int parse_authenticate_response(http_server *server)
-{
- /*
- * If we think that we've completed authentication (ie, we've either
- * sent a basic credential or we've sent the NTLM/Negotiate response)
- * but we've got an authentication request from the server then our
- * last authentication did not succeed. Start over.
- */
- if (server->auth_context && auth_context_complete(server)) {
- free_auth_context(server);
-
- server->authenticated = 0;
- }
-
- /*
- * If we've begun authentication, give the challenge to the context.
- * Otherwise, set up the types to prepare credentials.
- */
- if (git_vector_length(&server->auth_challenges) == 0)
- return 0;
- else if (server->auth_context)
- return set_authentication_challenge(server);
- else
- return set_authentication_types(server);
-}
-
-static int on_header_ready(http_subtransport *t)
-{
- 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) {
- git_error_set(GIT_ERROR_NET, "multiple Content-Type headers");
- return -1;
- }
-
- t->content_type = git__strdup(git_buf_cstr(value));
- GIT_ERROR_CHECK_ALLOC(t->content_type);
- }
- else if (!strcasecmp("Content-Length", git_buf_cstr(name))) {
- if (t->content_length) {
- git_error_set(GIT_ERROR_NET, "multiple Content-Length headers");
- return -1;
- }
-
- t->content_length = git__strdup(git_buf_cstr(value));
- GIT_ERROR_CHECK_ALLOC(t->content_length);
- }
- else if (!strcasecmp("Proxy-Authenticate", git_buf_cstr(name))) {
- char *dup = git__strdup(git_buf_cstr(value));
- GIT_ERROR_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));
- GIT_ERROR_CHECK_ALLOC(dup);
-
- if (git_vector_insert(&t->server.auth_challenges, dup) < 0)
- return -1;
- }
- else if (!strcasecmp("Location", git_buf_cstr(name))) {
- if (t->location) {
- git_error_set(GIT_ERROR_NET, "multiple Location headers");
- return -1;
- }
-
- t->location = git__strdup(git_buf_cstr(value));
- GIT_ERROR_CHECK_ALLOC(t->location);
- }
-
- return 0;
-}
-
-static int on_header_field(http_parser *parser, const char *str, size_t len)
-{
- 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);
-
- if (git_buf_put(&t->parse_header_name, str, len) < 0)
- return t->parse_error = PARSE_ERROR_GENERIC;
-
- t->last_cb = FIELD;
- return 0;
-}
-
-static int on_header_value(http_parser *parser, const char *str, size_t len)
-{
- 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);
+ git_http_client *http_client;
+} http_subtransport;
- if (git_buf_put(&t->parse_header_value, str, len) < 0)
- return t->parse_error = PARSE_ERROR_GENERIC;
+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",
+ 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
+};
+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",
+ 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",
+ 1
+};
- t->last_cb = VALUE;
- return 0;
-}
+#define SERVER_TYPE_REMOTE "remote"
+#define SERVER_TYPE_PROXY "proxy"
-GIT_INLINE(void) free_cred(git_cred **cred)
-{
- if (*cred) {
- git_cred_free(*cred);
- (*cred) = NULL;
- }
-}
+#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport)
static int apply_url_credentials(
git_cred **cred,
@@ -438,1096 +114,466 @@ static int apply_url_credentials(
return GIT_PASSTHROUGH;
}
-static int init_auth(http_server *server)
+GIT_INLINE(void) free_cred(git_cred **cred)
{
- git_http_auth_scheme *s, *scheme = NULL;
- char *c, *challenge = NULL;
- size_t i;
- int error;
-
- git_vector_foreach(&server->auth_challenges, i, c) {
- s = scheme_for_challenge(c, server->cred);
-
- if (s && !!(s->credtypes & server->credtypes)) {
- scheme = s;
- challenge = c;
- break;
- }
- }
-
- if (!scheme) {
- git_error_set(GIT_ERROR_NET, "no authentication mechanism could be negotiated");
- return -1;
+ if (*cred) {
+ git_cred_free(*cred);
+ (*cred) = NULL;
}
-
- if ((error = scheme->init_context(&server->auth_context, &server->url)) == GIT_PASSTHROUGH)
- return 0;
- else if (error < 0)
- return error;
-
- if (server->auth_context->set_challenge &&
- (error = server->auth_context->set_challenge(server->auth_context, challenge)) < 0)
- return error;
-
- return 0;
}
-static int on_auth_required(
- http_parser *parser,
+static int handle_auth(
http_server *server,
+ const char *server_type,
const char *url,
- const char *type,
+ unsigned int allowed_schemetypes,
+ unsigned int allowed_credtypes,
git_cred_acquire_cb callback,
void *callback_payload)
{
- parser_context *ctx = (parser_context *) parser->data;
- http_subtransport *t = ctx->t;
int error = 1;
- if (parse_authenticate_response(server) < 0) {
- t->parse_error = PARSE_ERROR_GENERIC;
- return t->parse_error;
- }
-
- /* If we're in the middle of challenge/response auth, continue */
- if (parser->status_code == 407 || parser->status_code == 401) {
- if (server->auth_context && !auth_context_complete(server)) {
- t->parse_error = PARSE_ERROR_REPLAY;
- return 0;
- }
- }
-
- /* Enforce a reasonable cap on the number of replays */
- if (t->replay_count++ >= GIT_HTTP_REPLAY_MAX) {
- git_error_set(GIT_ERROR_NET, "too many redirects or authentication replays");
- return t->parse_error = PARSE_ERROR_GENERIC;
- }
-
- if (!server->credtypes) {
- git_error_set(GIT_ERROR_NET, "%s requested authentication but did not negotiate mechanisms", type);
- t->parse_error = PARSE_ERROR_GENERIC;
- return t->parse_error;
- }
-
- free_auth_context(server);
- free_cred(&server->cred);
+ if (server->cred)
+ free_cred(&server->cred);
/* Start with URL-specified credentials, if there were any. */
- if (!server->url_cred_presented && server->url.username && server->url.password) {
- error = apply_url_credentials(&server->cred, server->credtypes, server->url.username, server->url.password);
+ if ((allowed_credtypes & GIT_CREDTYPE_USERPASS_PLAINTEXT) &&
+ !server->url_cred_presented &&
+ server->url.username &&
+ server->url.password) {
+ error = apply_url_credentials(&server->cred, allowed_credtypes, server->url.username, server->url.password);
server->url_cred_presented = 1;
- if (error == GIT_PASSTHROUGH) {
- /* treat GIT_PASSTHROUGH as if callback isn't set */
+ /* 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, server->credtypes, callback_payload);
+ error = callback(&server->cred, url, server->url.username, allowed_credtypes, callback_payload);
- if (error == GIT_PASSTHROUGH) {
- /* treat GIT_PASSTHROUGH as if callback isn't set */
+ /* treat GIT_PASSTHROUGH as if callback isn't set */
+ if (error == GIT_PASSTHROUGH)
error = 1;
- }
}
if (error > 0) {
- git_error_set(GIT_ERROR_NET, "%s authentication required but no callback set",
- type);
- t->parse_error = PARSE_ERROR_GENERIC;
- return t->parse_error;
- } else if (error < 0) {
- t->error = error;
- t->parse_error = PARSE_ERROR_EXT;
- return t->parse_error;
- }
-
- assert(server->cred);
-
- if (!(server->cred->credtype & server->credtypes)) {
- git_error_set(GIT_ERROR_NET, "%s credential provider returned an invalid cred type", type);
- t->parse_error = PARSE_ERROR_GENERIC;
- return t->parse_error;
- }
-
- /* Successfully acquired a credential. Start an auth context. */
- if (init_auth(server) < 0) {
- t->parse_error = PARSE_ERROR_GENERIC;
- return t->parse_error;
+ git_error_set(GIT_ERROR_NET, "%s authentication required but no callback set", server_type);
+ error = -1;
}
- t->parse_error = PARSE_ERROR_REPLAY;
- return 0;
-}
+ if (!error)
+ server->auth_schemetypes = allowed_schemetypes;
-static void on_auth_success(http_server *server)
-{
- server->url_cred_presented = 0;
- server->authenticated = 1;
+ return error;
}
-static int on_headers_complete(http_parser *parser)
+GIT_INLINE(int) handle_remote_auth(
+ http_stream *stream,
+ git_http_response *response)
{
- parser_context *ctx = (parser_context *) parser->data;
- http_subtransport *t = ctx->t;
- http_stream *s = ctx->s;
- git_buf buf = GIT_BUF_INIT;
-
- /* Both parse_header_name and parse_header_value are populated
- * and ready for consumption. */
- if (t->last_cb == VALUE && on_header_ready(t) < 0)
- return t->parse_error = PARSE_ERROR_GENERIC;
-
- /* Check for a proxy authentication failure. */
- if (parser->status_code == 407 && get_verb == s->verb)
- return on_auth_required(
- parser,
- &t->proxy,
- t->proxy_opts.url,
- SERVER_TYPE_PROXY,
- t->proxy_opts.credentials,
- t->proxy_opts.payload);
- else
- on_auth_success(&t->proxy);
-
- /* Check for an authentication failure. */
- if (parser->status_code == 401 && get_verb == s->verb)
- return on_auth_required(
- parser,
- &t->server,
- t->owner->url,
- SERVER_TYPE_REMOTE,
- t->owner->cred_acquire_cb,
- t->owner->cred_acquire_payload);
- else
- on_auth_success(&t->server);
-
- /* 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 ||
- parser->status_code == 308) &&
- t->location) {
-
- if (git_net_url_apply_redirect(&t->server.url, t->location, s->service_url) < 0)
- return t->parse_error = PARSE_ERROR_GENERIC;
-
- t->connected = 0;
- t->parse_error = PARSE_ERROR_REPLAY;
- return 0;
- }
-
- /* Check for a 200 HTTP status code. */
- if (parser->status_code != 200) {
- git_error_set(GIT_ERROR_NET,
- "unexpected HTTP status code: %d",
- parser->status_code);
- return t->parse_error = PARSE_ERROR_GENERIC;
- }
-
- /* The response must contain a Content-Type header. */
- if (!t->content_type) {
- git_error_set(GIT_ERROR_NET, "no Content-Type header in response");
- return t->parse_error = PARSE_ERROR_GENERIC;
- }
+ http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
- /* 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_dispose(&buf);
- git_error_set(GIT_ERROR_NET,
- "invalid Content-Type: %s",
- t->content_type);
- return t->parse_error = PARSE_ERROR_GENERIC;
+ if (response->server_auth_credtypes == 0) {
+ git_error_set(GIT_ERROR_NET, "server requires authentication that we do not support");
+ return -1;
}
- git_buf_dispose(&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;
- t->keepalive = http_should_keep_alive(parser);
-
- return 0;
+ /* Otherwise, prompt for credentials. */
+ return handle_auth(
+ &transport->server,
+ SERVER_TYPE_REMOTE,
+ transport->owner->url,
+ response->server_auth_schemetypes,
+ response->server_auth_credtypes,
+ transport->owner->cred_acquire_cb,
+ transport->owner->cred_acquire_payload);
}
-static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
+GIT_INLINE(int) handle_proxy_auth(
+ http_stream *stream,
+ git_http_response *response)
{
- parser_context *ctx = (parser_context *) parser->data;
- http_subtransport *t = ctx->t;
-
- /* If there's no buffer set, we're explicitly ignoring the body. */
- if (ctx->buffer) {
- if (ctx->buf_size < len) {
- git_error_set(GIT_ERROR_NET, "can't fit data in the buffer");
- return t->parse_error = PARSE_ERROR_GENERIC;
- }
+ http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
- memcpy(ctx->buffer, str, len);
- ctx->buffer += len;
- ctx->buf_size -= len;
+ if (response->proxy_auth_credtypes == 0) {
+ git_error_set(GIT_ERROR_NET, "proxy requires authentication that we do not support");
+ return -1;
}
- *(ctx->bytes_read) += len;
-
- return 0;
+ /* Otherwise, prompt for credentials. */
+ return handle_auth(
+ &transport->proxy,
+ SERVER_TYPE_PROXY,
+ transport->owner->proxy.url,
+ response->server_auth_schemetypes,
+ response->proxy_auth_credtypes,
+ transport->owner->proxy.credentials,
+ transport->owner->proxy.payload);
}
-static void clear_parser_state(http_subtransport *t)
-{
- http_parser_init(&t->parser, HTTP_RESPONSE);
- gitno_buffer_setup_fromstream(t->server.stream,
- &t->parse_buffer,
- t->parse_buffer_data,
- sizeof(t->parse_buffer_data));
-
- t->last_cb = NONE;
- t->parse_error = 0;
- t->parse_finished = 0;
- t->keepalive = 0;
-
- git_buf_dispose(&t->parse_header_name);
- git_buf_init(&t->parse_header_name, 0);
-
- git_buf_dispose(&t->parse_header_value);
- git_buf_init(&t->parse_header_value, 0);
-
- 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->proxy.auth_challenges);
- git_vector_free_deep(&t->server.auth_challenges);
-}
-static int write_chunk(git_stream *io, const char *buffer, size_t len)
+static int handle_response(
+ bool *complete,
+ http_stream *stream,
+ git_http_response *response,
+ bool allow_replay)
{
- git_buf buf = GIT_BUF_INIT;
-
- /* Chunk header */
- git_buf_printf(&buf, "%" PRIxZ "\r\n", len);
-
- if (git_buf_oom(&buf))
- return -1;
+ http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
+ int error;
- if (git_stream__write_full(io, buf.ptr, buf.size, 0) < 0) {
- git_buf_dispose(&buf);
- return -1;
- }
+ *complete = false;
- git_buf_dispose(&buf);
+ if (allow_replay && git_http_response_is_redirect(response)) {
+ if (!response->location) {
+ git_error_set(GIT_ERROR_NET, "redirect without location");
+ return -1;
+ }
- /* Chunk body */
- if (len > 0 && git_stream__write_full(io, buffer, len, 0) < 0)
- return -1;
+ if (git_net_url_apply_redirect(&transport->server.url, response->location, stream->service->url) < 0) {
+ return -1;
+ }
- /* Chunk footer */
- if (git_stream__write_full(io, "\r\n", 2, 0) < 0)
+ return 0;
+ } else if (git_http_response_is_redirect(response)) {
+ git_error_set(GIT_ERROR_NET, "unexpected redirect");
return -1;
+ }
- return 0;
-}
-
-static int load_proxy_config(http_subtransport *t)
-{
- int error;
-
- switch (t->owner->proxy.type) {
- case GIT_PROXY_NONE:
+ /* If we're in the middle of challenge/response auth, continue. */
+ if (allow_replay && response->resend_credentials) {
return 0;
-
- case GIT_PROXY_AUTO:
- git__free(t->proxy_url);
- t->proxy_url = NULL;
-
- git_proxy_options_init(&t->proxy_opts, GIT_PROXY_OPTIONS_VERSION);
-
- if ((error = git_remote__get_http_proxy(t->owner->owner,
- !strcmp(t->server.url.scheme, "https"), &t->proxy_url)) < 0)
+ } else if (allow_replay && response->status == 401) {
+ if ((error = handle_remote_auth(stream, response)) < 0)
return error;
- if (!t->proxy_url)
- return 0;
-
- 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;
+ return git_http_client_skip_body(transport->http_client);
+ } else if (allow_replay && response->status == 407) {
+ if ((error = handle_proxy_auth(stream, response)) < 0)
+ return error;
- default:
- assert(0);
+ return git_http_client_skip_body(transport->http_client);
+ } else if (response->status == 401 || response->status == 407) {
+ git_error_set(GIT_ERROR_NET, "unexpected authentication failure");
return -1;
}
- git_net_url_dispose(&t->proxy.url);
-
- return git_net_url_parse(&t->proxy.url, t->proxy_opts.url);
-}
-
-static int check_certificate(
- git_stream *stream,
- git_net_url *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 ((error = git_stream_certificate(&cert, stream)) < 0)
- return error;
-
- git_error_state_capture(&last_error, GIT_ECERTIFICATE);
-
- error = cert_cb(cert, is_valid, url->host, cert_cb_payload);
-
- if (error == GIT_PASSTHROUGH && !is_valid)
- return git_error_state_restore(&last_error);
- else if (error == GIT_PASSTHROUGH)
- error = 0;
- else if (error && !git_error_last())
- git_error_set(GIT_ERROR_NET, "user rejected certificate for %s", url->host);
-
- git_error_state_free(&last_error);
- return error;
-}
-
-static int stream_connect(
- git_stream *stream,
- git_net_url *url,
- git_transport_certificate_check_cb cert_cb,
- void *cb_payload)
-{
- int error;
-
- GIT_ERROR_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)
+ if (response->status != 200) {
+ git_error_set(GIT_ERROR_NET, "unexpected http status code: %d", response->status);
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;
-
- /* Both parse_header_name and parse_header_value are populated
- * and ready for consumption. */
- if (t->last_cb == VALUE && 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) < 0)
- return t->parse_error = PARSE_ERROR_GENERIC;
-
- /* If we're in the middle of challenge/response auth, continue */
- if (parser->status_code == 407) {
- if (t->proxy.auth_context && !auth_context_complete(&t->proxy)) {
- t->parse_error = PARSE_ERROR_REPLAY;
- return 0;
- }
}
- /* Enforce a reasonable cap on the number of replays */
- if (t->replay_count++ >= GIT_HTTP_REPLAY_MAX) {
- git_error_set(GIT_ERROR_NET, "too many redirects or authentication replays");
- return t->parse_error = PARSE_ERROR_GENERIC;
+ /* The response must contain a Content-Type header. */
+ if (!response->content_type) {
+ git_error_set(GIT_ERROR_NET, "no content-type header in response");
+ return -1;
}
- /* Check for a proxy authentication failure. */
- if (parser->status_code == 407)
- return on_auth_required(
- parser,
- &t->proxy,
- t->proxy_opts.url,
- SERVER_TYPE_PROXY,
- t->proxy_opts.credentials,
- t->proxy_opts.payload);
-
- if (parser->status_code != 200) {
- git_error_set(GIT_ERROR_NET, "unexpected status code from proxy: %d",
- parser->status_code);
- return t->parse_error = PARSE_ERROR_GENERIC;
+ /* The Content-Type header must match our expectation. */
+ if (strcmp(response->content_type, stream->service->response_type) != 0) {
+ git_error_set(GIT_ERROR_NET, "invalid content-type: '%s'", response->content_type);
+ return -1;
}
- if (!t->content_length || strcmp(t->content_length, "0") == 0)
- t->parse_finished = 1;
-
+ *complete = true;
+ stream->state = HTTP_STATE_RECEIVING_RESPONSE;
return 0;
}
-static int proxy_connect(
- git_stream **out, git_stream *proxy_stream, http_subtransport *t)
+static int lookup_proxy(
+ bool *out_use,
+ http_subtransport *transport)
{
- git_buf request = GIT_BUF_INIT;
- static http_parser_settings proxy_parser_settings = {0};
- size_t bytes_read = 0, bytes_parsed;
- parser_context ctx;
- bool auth_replay;
- int error;
-
- /* 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;
-
-replay:
- clear_parser_state(t);
-
- auth_replay = false;
-
- gitno_buffer_setup_fromstream(proxy_stream,
- &t->parse_buffer,
- t->parse_buffer_data,
- sizeof(t->parse_buffer_data));
-
- if ((error = gen_connect_req(&request, t)) < 0)
- goto done;
-
- if ((error = git_stream__write_full(proxy_stream, request.ptr,
- request.size, 0)) < 0)
- goto done;
-
- git_buf_dispose(&request);
+ const char *proxy;
+ git_remote *remote;
+ bool use_ssl;
+ char *config = NULL;
+ int error = 0;
- while (!bytes_read && !t->parse_finished) {
- t->parse_buffer.offset = 0;
+ *out_use = false;
+ git_net_url_dispose(&transport->proxy.url);
- if ((error = gitno_recv(&t->parse_buffer)) < 0) {
- goto done;
- } else if (error == 0 && t->request_count > 0) {
- /* Server closed a keep-alive socket; reconnect. */
- auth_replay = true;
- goto done;
- } else if (error == 0) {
- git_error_set(GIT_ERROR_NET, "unexpected disconnection from server");
- error = -1;
- goto done;
- }
+ switch (transport->owner->proxy.type) {
+ case GIT_PROXY_SPECIFIED:
+ proxy = transport->owner->proxy.url;
+ break;
- /*
- * 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) {
- git_error_set(GIT_ERROR_NET, "proxy server sent unsupported redirect during CONNECT");
- error = -1;
- goto done;
- }
+ case GIT_PROXY_AUTO:
+ remote = transport->owner->owner;
+ use_ssl = !strcmp(transport->server.url.scheme, "https");
- /* Replay the request with authentication headers. */
- if (PARSE_ERROR_REPLAY == t->parse_error) {
- auth_replay = true;
- } else if (t->parse_error < 0) {
- error = t->parse_error == PARSE_ERROR_EXT ? PARSE_ERROR_EXT : -1;
- goto done;
- }
+ error = git_remote__get_http_proxy(remote, use_ssl, &config);
- if (bytes_parsed != t->parse_buffer.offset) {
- git_error_set(GIT_ERROR_NET,
- "HTTP parser error: %s",
- http_errno_description((enum http_errno)t->parser.http_errno));
- error = -1;
+ if (error || !config)
goto done;
- }
- }
-
- t->request_count++;
- if (auth_replay) {
- if (t->keepalive && t->parse_finished)
- goto replay;
+ proxy = config;
+ break;
- return PARSE_ERROR_REPLAY;
+ default:
+ return 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);
+ if (!proxy ||
+ (error = git_net_url_parse(&transport->proxy.url, proxy)) < 0)
+ goto done;
- /*
- * 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;
- t->request_count = 0;
+ *out_use = true;
done:
+ git__free(config);
return error;
}
-static void reset_auth_connection(http_server *server)
-{
- /*
- * If we've authenticated and we're doing "normal"
- * authentication with a request affinity (Basic, Digest)
- * then we want to _keep_ our context, since authentication
- * survives even through non-keep-alive connections. If
- * we've authenticated and we're doing connection-based
- * authentication (NTLM, Negotiate) - indicated by the presence
- * of an `is_complete` callback - then we need to restart
- * authentication on a new connection.
- */
-
- if (server->authenticated &&
- server->auth_context &&
- server->auth_context->connection_affinity) {
- server->prior_authtype = server->auth_context->type;
-
- free_auth_context(server);
-
- server->url_cred_presented = 0;
- server->authenticated = 0;
- }
-}
-
-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)
{
- git_net_url *url;
- git_stream *proxy_stream = NULL, *stream = NULL;
- git_transport_certificate_check_cb cert_cb;
- void *cb_payload;
+ http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
+ bool use_proxy = false;
int error;
-auth_replay:
- if (t->connected && t->keepalive && t->parse_finished)
- return 0;
-
- if ((error = load_proxy_config(t)) < 0)
+ if ((error = git_net_url_joinpath(url,
+ &transport->server.url, stream->service->url)) < 0 ||
+ (error = lookup_proxy(&use_proxy, transport)) < 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;
- }
-
- reset_auth_connection(&t->server);
- reset_auth_connection(&t->proxy);
-
- t->connected = 0;
- t->keepalive = 0;
- t->request_count = 0;
+ 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;
- 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 (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;
}
- if (strcmp(url->scheme, "https") == 0)
- 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 &&
- strcmp(t->server.url.scheme, "https") == 0) {
- proxy_stream = stream;
- stream = NULL;
-
- error = proxy_connect(&stream, proxy_stream, t);
-
- if (error == PARSE_ERROR_REPLAY) {
- git_stream_close(proxy_stream);
- git_stream_free(proxy_stream);
- goto auth_replay;
- } else if (error < 0) {
- goto on_error;
- }
- }
-
- t->proxy.stream = proxy_stream;
- t->server.stream = stream;
- t->connected = 1;
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;
}
+/*
+ * 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)
-{
- http_stream *s = (http_stream *)stream;
- http_subtransport *t = OWNING_SUBTRANSPORT(s);
- parser_context ctx;
- size_t bytes_parsed;
- git_buf request = GIT_BUF_INIT;
- bool auth_replay;
- int error = 0;
-
-replay:
- *bytes_read = 0;
- auth_replay = false;
-
- assert(t->connected);
-
- if (!s->sent_request) {
- git_buf_clear(&request);
- clear_parser_state(t);
-
- if ((error = gen_request(&request, s, 0)) < 0 ||
- (error = git_stream__write_full(t->server.stream, request.ptr, request.size, 0)) < 0)
- goto done;
-
- s->sent_request = 1;
- }
-
- if (!s->received_response) {
- if (s->chunked) {
- assert(s->verb == post_verb);
-
- /* Flush, if necessary */
- if (s->chunk_buffer_len > 0) {
- if ((error = write_chunk(t->server.stream, s->chunk_buffer, s->chunk_buffer_len)) < 0)
- goto done;
-
- s->chunk_buffer_len = 0;
- }
+ 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;
- /* Write the final chunk. */
- if ((error = git_stream__write_full(t->server.stream,
- "0\r\n\r\n", 5, 0)) < 0)
- goto done;
- }
+ *out_len = 0;
- s->received_response = 1;
+ if (stream->state == HTTP_STATE_NONE) {
+ stream->state = HTTP_STATE_SENDING_REQUEST;
+ stream->replay_count = 0;
}
- while (!*bytes_read && !t->parse_finished) {
- size_t data_offset;
-
- /*
- * 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 ((error = gitno_recv(&t->parse_buffer)) < 0) {
- goto done;
- } else if (error == 0 && t->request_count > 0) {
- /* Server closed a keep-alive socket; reconnect. */
- auth_replay = true;
- goto done;
- } else if (error == 0) {
- git_error_set(GIT_ERROR_NET, "unexpected disconnection from server");
- error = -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;
- }
- /*
- * This call to http_parser_execute will result in invocations
- * of the on_* family of callbacks, including on_body_fill_buffer
- * which will write into the target buffer. Set up the buffer
- * for it to write into _unless_ we got an auth failure; in
- * that case we only care about the headers and don't need to
- * bother copying the body.
- */
- ctx.t = t;
- ctx.s = s;
- ctx.buffer = auth_replay ? NULL : buffer;
- ctx.buf_size = auth_replay ? 0 : buf_size;
- 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,
- &t->settings,
- t->parse_buffer.data + data_offset,
- t->parse_buffer.offset - data_offset);
-
- t->parser.data = NULL;
-
- /* On a 401, read the rest of the response then retry. */
- if (t->parse_error == PARSE_ERROR_REPLAY) {
- auth_replay = true;
- } else if (t->parse_error == PARSE_ERROR_EXT) {
- error = t->error;
- goto done;
- } else if (t->parse_error < 0) {
- error = -1;
- goto done;
- }
+ if (complete)
+ break;
- if (bytes_parsed != t->parse_buffer.offset - data_offset) {
- git_error_set(GIT_ERROR_NET,
- "HTTP parser error: %s",
- http_errno_description((enum http_errno)t->parser.http_errno));
- error = -1;
- goto done;
- }
+ stream->replay_count++;
}
- t->request_count++;
+ if (stream->state == HTTP_STATE_SENDING_REQUEST) {
+ git_error_set(GIT_ERROR_NET, "too many redirects or authentication replays");
+ error = -1;
+ goto done;
+ }
- if (auth_replay) {
- s->sent_request = 0;
+ assert (stream->state == HTTP_STATE_RECEIVING_RESPONSE);
- if ((error = http_connect(t)) < 0)
- return error;
+ error = git_http_client_read_body(transport->http_client, buffer, buffer_size);
- goto replay;
+ if (error > 0) {
+ *out_len = error;
+ error = 0;
}
done:
- git_buf_dispose(&request);
+ git_net_url_dispose(&url);
+ git_net_url_dispose(&proxy_url);
+ git_http_response_dispose(&response);
+
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 = GIT_CONTAINER_OF(stream, http_stream, parent);
- http_subtransport *t = OWNING_SUBTRANSPORT(s);
-
- assert(t->connected);
+ 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;
- /* Send the request, if necessary */
- if (!s->sent_request) {
- git_buf request = GIT_BUF_INIT;
+ while (stream->state == HTTP_STATE_NONE &&
+ stream->replay_count < GIT_HTTP_REPLAY_MAX) {
- clear_parser_state(t);
+ git_net_url_dispose(&url);
+ git_http_response_dispose(&response);
- if (gen_request(&request, s, 0) < 0)
- return -1;
+ if ((error = generate_request(&url, &request, stream, len)) < 0 ||
+ (error = git_http_client_send_request(
+ transport->http_client, &request)) < 0)
+ goto done;
- if (git_stream__write_full(t->server.stream, request.ptr,
- request.size, 0) < 0) {
- git_buf_dispose(&request);
- return -1;
+ 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_dispose(&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->server.stream,
- s->chunk_buffer, s->chunk_buffer_len) < 0)
- return -1;
-
- s->chunk_buffer_len = 0;
- }
-
- /* Write chunk directly */
- if (write_chunk(t->server.stream, buffer, len) < 0)
- return -1;
+ if (stream->state == HTTP_STATE_NONE) {
+ git_error_set(GIT_ERROR_NET,
+ "too many redirects or authentication replays");
+ error = -1;
+ 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);
- GIT_ERROR_CHECK_ALLOC(s->chunk_buffer);
- }
-
- memcpy(s->chunk_buffer + s->chunk_buffer_len, buffer, count);
- s->chunk_buffer_len += count;
- buffer += count;
- len -= count;
-
- /* Is the buffer full? If so, then flush */
- if (CHUNK_SIZE == s->chunk_buffer_len) {
- if (write_chunk(t->server.stream,
- s->chunk_buffer, s->chunk_buffer_len) < 0)
- return -1;
-
- s->chunk_buffer_len = 0;
+ assert(stream->state == HTTP_STATE_SENDING_REQUEST);
- if (len > 0) {
- memcpy(s->chunk_buffer, buffer, len);
- s->chunk_buffer_len = len;
- }
- }
- }
+ error = git_http_client_send_body(transport->http_client, buffer, 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 = GIT_CONTAINER_OF(stream, http_stream, parent);
- http_subtransport *t = OWNING_SUBTRANSPORT(s);
- git_buf request = GIT_BUF_INIT;
-
- assert(t->connected);
-
- if (s->sent_request) {
- git_error_set(GIT_ERROR_NET, "subtransport configured for only one write");
- return -1;
- }
-
- clear_parser_state(t);
+ http_stream *stream = (http_stream *)s;
+ http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
+ git_http_response response = {0};
+ bool complete;
+ int error;
- if (gen_request(&request, s, len) < 0)
- return -1;
+ *out_len = 0;
- if (git_stream__write_full(t->server.stream, request.ptr, request.size, 0) < 0)
- goto on_error;
+ if (stream->state == HTTP_STATE_SENDING_REQUEST) {
+ if ((error = git_http_client_read_response(&response, transport->http_client)) < 0 ||
+ (error = handle_response(&complete, stream, &response, false)) < 0)
+ goto done;
- if (len && git_stream__write_full(t->server.stream, buffer, len, 0) < 0)
- goto on_error;
+ assert(complete);
+ stream->state = HTTP_STATE_RECEIVING_RESPONSE;
+ }
- git_buf_dispose(&request);
- s->sent_request = 1;
+ error = git_http_client_read_body(transport->http_client, buffer, buffer_size);
- return 0;
+ if (error > 0) {
+ *out_len = error;
+ error = 0;
+ }
-on_error:
- git_buf_dispose(&request);
- return -1;
+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);
-
- if (s->chunk_buffer)
- git__free(s->chunk_buffer);
-
git__free(s);
}
-static int http_stream_alloc(http_subtransport *t,
- git_smart_subtransport_stream **stream)
-{
- http_stream *s;
-
- if (!stream)
- return -1;
-
- s = git__calloc(sizeof(http_stream), 1);
- GIT_ERROR_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)
+static const http_service *select_service(git_smart_service_t action)
{
- 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;
-
- return 0;
-}
-
-static int http_uploadpack(
- 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_service_url;
- s->verb = post_verb;
-
- return 0;
-}
-
-static int http_receivepack_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 = receive_pack_service;
- s->service_url = receive_pack_ls_service_url;
- s->verb = get_verb;
-
- return 0;
-}
-
-static int http_receivepack(
- http_subtransport *t,
- git_smart_subtransport_stream **stream)
-{
- http_stream *s;
-
- if (http_stream_alloc(t, stream) < 0)
- return -1;
-
- s = (http_stream *)*stream;
-
- /* Use Transfer-Encoding: chunked for this request */
- s->chunked = 1;
- s->parent.write = http_stream_write_chunked;
-
- s->service = receive_pack_service;
- s->service_url = receive_pack_service_url;
- s->verb = post_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_action(
- git_smart_subtransport_stream **stream,
- git_smart_subtransport *subtransport,
+ git_smart_subtransport_stream **out,
+ git_smart_subtransport *t,
const char *url,
git_smart_service_t action)
{
- http_subtransport *t = GIT_CONTAINER_OF(subtransport, http_subtransport, parent);
- int ret;
+ http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent);
+ http_stream *stream;
+ const http_service *service;
+ int error;
+
+ assert(out && t);
- assert(stream);
+ *out = NULL;
/*
* If we've seen a redirect then preserve the location that we've
@@ -1536,103 +582,89 @@ static int http_action(
* 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 = git_net_url_parse(&t->server.url, url)) < 0)
- return ret;
-
- assert(t->server.url.host && t->server.url.port && t->server.url.path);
+ if (!git_net_url_valid(&transport->server.url) &&
+ (error = git_net_url_parse(&transport->server.url, url)) < 0)
+ return error;
- if ((ret = http_connect(t)) < 0)
- return ret;
+ if ((service = select_service(action)) == NULL) {
+ git_error_set(GIT_ERROR_NET, "invalid action");
+ return -1;
+ }
- switch (action) {
- case GIT_SERVICE_UPLOADPACK_LS:
- return http_uploadpack_ls(t, stream);
+ stream = git__calloc(sizeof(http_stream), 1);
+ GIT_ERROR_CHECK_ALLOC(stream);
- case GIT_SERVICE_UPLOADPACK:
- return http_uploadpack(t, stream);
+ if (!transport->http_client) {
+ git_http_client_options opts = {0};
- case GIT_SERVICE_RECEIVEPACK_LS:
- return http_receivepack_ls(t, stream);
+ opts.server_certificate_check_cb = transport->owner->certificate_check_cb;
+ opts.server_certificate_check_payload = transport->owner->message_cb_payload;
+ opts.proxy_certificate_check_cb = transport->owner->proxy.certificate_check;
+ opts.proxy_certificate_check_payload = transport->owner->proxy.payload;
- case GIT_SERVICE_RECEIVEPACK:
- return http_receivepack(t, stream);
+ if (git_http_client_new(&transport->http_client, &opts) < 0)
+ return -1;
}
- *stream = NULL;
- return -1;
-}
-
-static int http_close(git_smart_subtransport *subtransport)
-{
- http_subtransport *t = GIT_CONTAINER_OF(subtransport, http_subtransport, parent);
-
- clear_parser_state(t);
+ stream->service = service;
+ stream->parent.subtransport = &transport->parent;
- t->connected = 0;
-
- if (t->server.stream) {
- git_stream_close(t->server.stream);
- git_stream_free(t->server.stream);
- t->server.stream = NULL;
+ 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;
}
- if (t->proxy.stream) {
- git_stream_close(t->proxy.stream);
- git_stream_free(t->proxy.stream);
- t->proxy.stream = NULL;
- }
+ stream->parent.free = http_stream_free;
- free_cred(&t->server.cred);
- free_cred(&t->proxy.cred);
+ *out = (git_smart_subtransport_stream *)stream;
+ return 0;
+}
- free_auth_context(&t->server);
- free_auth_context(&t->proxy);
+static int http_close(git_smart_subtransport *t)
+{
+ http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent);
- t->server.url_cred_presented = false;
- t->proxy.url_cred_presented = false;
+ free_cred(&transport->server.cred);
+ free_cred(&transport->proxy.cred);
- git_net_url_dispose(&t->server.url);
- git_net_url_dispose(&t->proxy.url);
+ transport->server.url_cred_presented = false;
+ transport->proxy.url_cred_presented = false;
- git__free(t->proxy_url);
- t->proxy_url = NULL;
+ 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 = GIT_CONTAINER_OF(subtransport, http_subtransport, parent);
+ http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent);
- http_close(subtransport);
- git__free(t);
+ 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 *t;
+ http_subtransport *transport;
GIT_UNUSED(param);
- if (!out)
- return -1;
-
- t = git__calloc(sizeof(http_subtransport), 1);
- GIT_ERROR_CHECK_ALLOC(t);
+ assert(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;
}
diff --git a/src/transports/http.h b/src/transports/http.h
index 6f698c9dd..c02109cec 100644
--- a/src/transports/http.h
+++ b/src/transports/http.h
@@ -9,6 +9,7 @@
#define INCLUDE_transports_http_h__
#include "buffer.h"
+#include "httpclient.h"
#define GIT_HTTP_REPLAY_MAX 15