#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 "auth_ntlm.h"
#include "streams/tls.h"
#include "streams/socket.h"
git_http_auth_scheme auth_schemes[] = {
{ GIT_AUTHTYPE_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, git_http_auth_negotiate },
{ GIT_AUTHTYPE_NTLM, "NTLM", GIT_CREDTYPE_USERPASS_PLAINTEXT, git_http_auth_ntlm },
{ 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";
#define AUTH_HEADER_SERVER "Authorization"
#define AUTH_HEADER_PROXY "Proxy-Authorization"
#define SERVER_TYPE_REMOTE "remote"
#define SERVER_TYPE_PROXY "proxy"
#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport)
#define PARSE_ERROR_GENERIC -1
#define PARSE_ERROR_REPLAY -2
#define PARSE_ERROR_EXT -3
#define CHUNK_SIZE 4096
enum last_cb {
NONE,
FIELD,
VALUE
};
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;
} http_stream;
typedef struct {
git_net_url url;
git_stream *stream;
git_http_authtype_t authtypes;
git_credtype_t credtypes;
git_cred *cred;
unsigned url_cred_presented : 1,
authenticated : 1;
git_vector auth_challenges;
git_http_auth_context *auth_context;
} 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;
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;
char *buffer;
size_t buf_size;
size_t *bytes_read;
} parser_context;
static git_http_auth_scheme *scheme_for_challenge(const char *challenge)
{
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;
size_t scheme_len;
scheme_len = strlen(scheme_name);
if (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 : "/";
size_t i;
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, s->service_url);
else
git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n",
s->verb, path, s->service_url);
git_buf_puts(buf, "User-Agent: ");
git_http__user_agent(buf);
git_buf_puts(buf, "\r\n");
git_buf_printf(buf, "Host: %s", t->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]);
}
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) {
server->authtypes |= scheme->type;
server->credtypes |= scheme->credtypes;
}
}
return 0;
}
static bool auth_context_complete(http_server *server)
{
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 (server->auth_context && auth_context_complete(server)) {
free_auth_context(server);
server->authenticated = 0;
}
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;
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);
if (git_buf_put(&t->parse_header_value, str, len) < 0)
return t->parse_error = PARSE_ERROR_GENERIC;
t->last_cb = VALUE;
return 0;
}
GIT_INLINE(void) free_cred(git_cred **cred)
{
if (*cred) {
git_cred_free(*cred);
(*cred) = NULL;
}
}
static int apply_url_credentials(
git_cred **cred,
unsigned int allowed_types,
const char *username,
const char *password)
{
if (allowed_types & GIT_CREDTYPE_USERPASS_PLAINTEXT)
return git_cred_userpass_plaintext_new(cred, username, password);
if ((allowed_types & GIT_CREDTYPE_DEFAULT) && *username == '\0' && *password == '\0')
return git_cred_default_new(cred);
return GIT_PASSTHROUGH;
}
static int init_auth(http_server *server)
{
git_http_auth_scheme *s, *scheme = NULL;
char *c, *challenge = NULL;
size_t i;
git_vector_foreach(&server->auth_challenges, i, c) {
s = scheme_for_challenge(c);
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 (scheme->init_context(&server->auth_context, &server->url) < 0)
return -1;
if (server->auth_context->set_challenge &&
server->auth_context->set_challenge(server->auth_context, challenge) < 0)
return -1;
return 0;
}
static int on_auth_required(
http_parser *parser,
http_server *server,
const char *url,
const char *type,
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 (parser->status_code == 407 || parser->status_code == 401) {
if (server->auth_context && !auth_context_complete(server)) {
t->parse_error = PARSE_ERROR_REPLAY;
return 0;
}
}
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->url_cred_presented && server->url.username && server->url.password) {
error = apply_url_credentials(&server->cred, server->credtypes, server->url.username, server->url.password);
server->url_cred_presented = 1;
if (error == GIT_PASSTHROUGH) {
error = 1;
}
}
if (error > 0 && callback) {
error = callback(&server->cred, url, server->url.username, server->credtypes, callback_payload);
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;
}
if (init_auth(server) < 0) {
t->parse_error = PARSE_ERROR_GENERIC;
return t->parse_error;
}
t->parse_error = PARSE_ERROR_REPLAY;
return 0;
}
static void on_auth_success(http_server *server)
{
server->url_cred_presented = 0;
server->authenticated = 1;
}
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;
if (t->last_cb == VALUE && on_header_ready(t) < 0)
return t->parse_error = PARSE_ERROR_GENERIC;
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);
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);
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 (gitno_connection_data_handle_redirect(&t->server.url, t->location, s->service_url) < 0)
return t->parse_error = PARSE_ERROR_GENERIC;
if (s->redirect_url)
git__free(s->redirect_url);
s->redirect_url = t->location;
t->location = NULL;
t->connected = 0;
t->parse_error = PARSE_ERROR_REPLAY;
return 0;
}
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;
}
if (!t->content_type) {
git_error_set(GIT_ERROR_NET, "no Content-Type header in response");
return t->parse_error = PARSE_ERROR_GENERIC;
}
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;
}
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;
}
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 (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;
}
memcpy(ctx->buffer, str, len);
ctx->buffer += len;
ctx->buf_size -= len;
}
*(ctx->bytes_read) += len;
return 0;
}
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)
{
git_buf buf = GIT_BUF_INIT;
git_buf_printf(&buf, "%" PRIxZ "\r\n", len);
if (git_buf_oom(&buf))
return -1;
if (git_stream__write_full(io, buf.ptr, buf.size, 0) < 0) {
git_buf_dispose(&buf);
return -1;
}
git_buf_dispose(&buf);
if (len > 0 && git_stream__write_full(io, buffer, len, 0) < 0)
return -1;
if (git_stream__write_full(io, "\r\n", 2, 0) < 0)
return -1;
return 0;
}
static int load_proxy_config(http_subtransport *t)
{
int error;
switch (t->owner->proxy.type) {
case GIT_PROXY_NONE:
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)
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;
default:
assert(0);
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)
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;
if (t->last_cb == VALUE && on_header_ready(t) < 0)
return t->parse_error = PARSE_ERROR_GENERIC;
if (parse_authenticate_response(&t->proxy) < 0)
return t->parse_error = PARSE_ERROR_GENERIC;
if (parser->status_code == 407) {
if (t->proxy.auth_context && !auth_context_complete(&t->proxy)) {
t->parse_error = PARSE_ERROR_REPLAY;
return 0;
}
}
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 (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;
}
if (!t->content_length || strcmp(t->content_length, "0") == 0)
t->parse_finished = 1;
return 0;
}
static int proxy_connect(
git_stream **out, git_stream *proxy_stream, http_subtransport *t)
{
git_buf request = GIT_BUF_INIT;
static http_parser_settings proxy_parser_settings = {0};
size_t bytes_read = 0, bytes_parsed;
parser_context ctx;
bool auth_replay;
int error;
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);
while (!bytes_read && !t->parse_finished) {
t->parse_buffer.offset = 0;
if ((error = gitno_recv(&t->parse_buffer)) < 0) {
goto done;
} else if (error == 0 && t->request_count > 0) {
auth_replay = true;
goto done;
} else if (error == 0) {
git_error_set(GIT_ERROR_NET, "unexpected disconnection from server");
error = -1;
goto done;
}
ctx.t = t;
ctx.s = NULL;
ctx.buffer = NULL;
ctx.buf_size = 0;
ctx.bytes_read = &bytes_read;
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;
if (t->location) {
git_error_set(GIT_ERROR_NET, "proxy server sent unsupported redirect during CONNECT");
error = -1;
goto done;
}
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;
}
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;
goto done;
}
}
t->request_count++;
if (auth_replay) {
if (t->keepalive && t->parse_finished)
goto replay;
return PARSE_ERROR_REPLAY;
}
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);
t->proxy_opts.type = GIT_PROXY_NONE;
t->replay_count = 0;
t->request_count = 0;
done:
return error;
}
static void reset_auth_connection(http_server *server)
{
if (server->authenticated &&
server->auth_context &&
server->auth_context->connection_affinity) {
free_auth_context(server);
server->url_cred_presented = 0;
server->authenticated = 0;
}
}
static int http_connect(http_subtransport *t)
{
git_net_url *url;
git_stream *proxy_stream = NULL, *stream = NULL;
git_transport_certificate_check_cb cert_cb;
void *cb_payload;
int error;
auth_replay:
if (t->connected && t->keepalive && t->parse_finished)
return 0;
if ((error = load_proxy_config(t)) < 0)
return error;
if (t->server.stream) {
git_stream_close(t->server.stream);
git_stream_free(t->server.stream);
t->server.stream = NULL;
}
if (t->proxy.stream) {
git_stream_close(t->proxy.stream);
git_stream_free(t->proxy.stream);
t->proxy.stream = NULL;
}
reset_auth_connection(&t->server);
reset_auth_connection(&t->proxy);
t->connected = 0;
t->keepalive = 0;
t->request_count = 0;
if (t->proxy_opts.type == GIT_PROXY_SPECIFIED) {
url = &t->proxy.url;
cert_cb = t->proxy_opts.certificate_check;
cb_payload = t->proxy_opts.payload;
} else {
url = &t->server.url;
cert_cb = t->owner->certificate_check_cb;
cb_payload = t->owner->message_cb_payload;
}
if (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;
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;
}
static int http_stream_read(
git_smart_subtransport_stream *stream,
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);
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;
}
if ((error = git_stream__write_full(t->server.stream,
"0\r\n\r\n", 5, 0)) < 0)
goto done;
}
s->received_response = 1;
}
while (!*bytes_read && !t->parse_finished) {
size_t data_offset;
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) {
auth_replay = true;
goto done;
} else if (error == 0) {
git_error_set(GIT_ERROR_NET, "unexpected disconnection from server");
error = -1;
goto done;
}
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;
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;
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 (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;
}
}
t->request_count++;
if (auth_replay) {
s->sent_request = 0;
if ((error = http_connect(t)) < 0)
return error;
goto replay;
}
done:
git_buf_dispose(&request);
return error;
}
static int http_stream_write_chunked(
git_smart_subtransport_stream *stream,
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);
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_full(t->server.stream, request.ptr,
request.size, 0) < 0) {
git_buf_dispose(&request);
return -1;
}
git_buf_dispose(&request);
s->sent_request = 1;
}
if (len > CHUNK_SIZE) {
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;
}
if (write_chunk(t->server.stream, buffer, len) < 0)
return -1;
}
else {
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;
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;
if (len > 0) {
memcpy(s->chunk_buffer, buffer, len);
s->chunk_buffer_len = len;
}
}
}
return 0;
}
static int http_stream_write_single(
git_smart_subtransport_stream *stream,
const char *buffer,
size_t 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);
if (gen_request(&request, s, len) < 0)
return -1;
if (git_stream__write_full(t->server.stream, request.ptr, request.size, 0) < 0)
goto on_error;
if (len && git_stream__write_full(t->server.stream, buffer, len, 0) < 0)
goto on_error;
git_buf_dispose(&request);
s->sent_request = 1;
return 0;
on_error:
git_buf_dispose(&request);
return -1;
}
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);
if (s->redirect_url)
git__free(s->redirect_url);
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)
{
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;
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;
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 = GIT_CONTAINER_OF(subtransport, http_subtransport, parent);
int ret;
assert(stream);
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 ((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)
{
http_subtransport *t = GIT_CONTAINER_OF(subtransport, http_subtransport, parent);
clear_parser_state(t);
t->connected = 0;
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;
}
free_cred(&t->server.cred);
free_cred(&t->proxy.cred);
free_auth_context(&t->server);
free_auth_context(&t->proxy);
t->server.url_cred_presented = false;
t->proxy.url_cred_presented = false;
git_net_url_dispose(&t->server.url);
git_net_url_dispose(&t->proxy.url);
git__free(t->proxy_url);
t->proxy_url = NULL;
return 0;
}
static void http_free(git_smart_subtransport *subtransport)
{
http_subtransport *t = GIT_CONTAINER_OF(subtransport, http_subtransport, parent);
http_close(subtransport);
git__free(t);
}
int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner, void *param)
{
http_subtransport *t;
GIT_UNUSED(param);
if (!out)
return -1;
t = git__calloc(sizeof(http_subtransport), 1);
GIT_ERROR_CHECK_ALLOC(t);
t->owner = (transport_smart *)owner;
t->parent.action = http_action;
t->parent.close = http_close;
t->parent.free = http_free;
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;
*out = (git_smart_subtransport *) t;
return 0;
}
#endif